APP下载
报价宝  ›  科技  › 

前端开发:小程式专案实战开发——模仿网易云音乐微信小程式

报价宝 来源:baojiabao.com 发布时间:2019-08-25 05:56:00 11月06日更新
报价宝综合消息前端开发:小程式专案实战开发——模仿网易云音乐微信小程式

连结:https://juejin.im/post/5d10c899f265da1bae390749

专案部分截图(Gif)

#开发前准备

VScode程式码编辑器。微信开发者工具ios网易云音乐(V5.9.1版本)网易云音乐API(阿里巴巴向量图示库)提供一些图示icon

tabBar部分

自定义tabBar

一般在开发中,微信小程式给我们的tabBar就能满足需求。但是,有些特别的需求必须使用自定义tabBar才能满足。 比如tabBar实现半透明。那么,如何才能自定义tabBar呢?

1.首先,在 app.json里的"tabBar"里宣告 "tabBar": { "custom": true }

2.接着在专案的根目录下新建一个custom-tab-bar资料夹。里面包含四个档案:

index.wxml

index.js

index.json

index.wxss

tabBar半透明

/* custom-tab-bar/index.wxss */

.tab-bar {

height:7%;

position: fixed;

bottom: 0;

left: 0;

right: 0;

height: 48px;

background:#FAFBFD;

opacity: 0.93;

display: flex;

padding-bottom: env(safe-area-inset-bottom);

}

API封装

一般我们https请求都是通过wx.request来请求,但是这种方法只能请求一次资料。于是新建了一个档案专门存放API。

// api.js

const API_BASE_URL = 'http://musicapi.leanapp.cn';

const request = (url, data) => {

let _url = API_BASE_URL + url;

return new Promise((resolve, reject) => {

wx.request({

url: _url,

method: "get",

data: data,

header: {

'Content-Type': 'application/x-www-form-urlencoded'

},

success(request) {

resolve(request.data)

},

fail(error) {

reject(error)

}

})

});

}

module.exports ={

gethotsongs:(data) =>{

return request('/search/hot',data)//热搜界面

},

searchSuggest:(data)=>{

return request('/search/suggest',data)//搜寻建议界面

},

searchResult:(data)=>{

return request('/search',data)//搜寻结果界面

},

getBanner:(data)=>{

return request('/banner',data)//个性推荐轮播

},

getsongsheet:(data)=>{

return request('/top/playlist',data)//热门歌单界面

},

getNewSong:(data)=>{

return request('/personalized/newsong',data)//最新音乐界面

},

getDjRadios:(data)=>{

return request('/dj/recommend',data)//电台推荐界面

},

getProgramRecommend:(data)=>{

return request('/program/recommend',data)//推荐节目界面

},

getRecommendType:(data)=>{

return request('/dj/recommend/type',data)//所有电台分类推荐

},

getRecommendMV:(data)=>{

return request('/personalized/mv',data)//推荐MV

},

getNewMv:(data)=>{

return request('/mv/first',data)//最新MV

},

getNewEst:(data)=>{

return request('/album/newest',data)//最新专辑

},

getTopList:(data)=>{

return request('/top/list',data)//排行榜

},

getDjList:(data)=>{

return request('/dj/catelist',data) //电台分类

},

getPay:(data)=>{

return request('/dj/paygift',data)//付费精品

},

getSonger:(data)=>{

return request('/toplist/artist',data)//歌手排行

}

}

api.js只能通过module.exports来暴露,那个页面要资料就从这拿。如果在哪个页面要用到它,还需要在头部引入一下:

const API = require('../../API/api')

以个性推荐轮播图为例,

getBanner: function() {

API.getBanner({

type: 2

}).then(res => {

if (res.code === 200) { //更加严谨

this.setData({

banner: res.banners

})

}

})

}

这样就把请求到的资料储存到banner中了。

搜寻部分

输入框样式

这里是引入了WEUI的样式:

1.下载weui.wxss

2.把下载好的weui.wxss放到根目录下。

3.在app.wxss中@import "weui.wxss";引入,这样可以使用微信提供的样式了。

4.WeUI样式库热门搜寻

上面已经提到我从api.js中拿资料。 // 从界面到获取到资料汇入到hotsongs

gethotsongs() {

API.gethotsongs({ type: 'new' }).then(res => {

wx.hideLoading()

if (res.code === 200) { //严谨

this.setData({

hotsongs: res.result.hots

})

}

})

}

搜寻历史

思路:当在输入框输入完成后-->失去焦点--> 利用wx.setStorageSync存进快取中-->wx.getStorageSync获取到并把它打印出来。 // input失去焦点函式

routeSearchResPage: function(e) {

console.log(e.detail.value)

let history = wx.getStorageSync("history") || [];

history.push(this.data.searchKey)

wx.setStorageSync("history", history);

},

//每次显示变动就去获取快取,给history,并for出来。

onShow: function () {

this.setData({

history: wx.getStorageSync("history") || []

})

},

清空搜寻历史

思路:×图示系结事件->拨出对话方块wx.showModal->确定则把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) {

}

}

})

},

实时搜寻建议

思路:实时获取输入框的值->把值传给搜寻建议API,发起网络请求->请求之后拿到搜寻建议->打印结果并隐藏其他元件只保留搜寻建议的元件(类似于Vue里的v-show) //获取input文字并且实时搜寻,动态隐藏元件

getsearchKey:function(e){

console.log(e.detail.value) //打印出输入框的值

let that = this;

if(e.detail.cursor != that.data.cursor){ //实时获取输入框的值

that.setData({

searchKey: e.detail.value

})

}

if(e.value!=""){ //元件的显示与隐藏

that.setData({

showView: false

})

} else{

that.setData({

showView: ""

})

}

if(e.detail.value!=""){ //解决 如果输入框的值为空时,传值给搜寻建议,会报错的bug

that.searchSuggest();

}

}

// 搜寻建议

searchSuggest(){

API.searchSuggest({ keywords: this.data.searchKey ,type:'mobile'}).then(res=>{

if(res.code === 200){

this.setData({

searchsuggest:res.result.allMatch

})

}

})

}

点选热搜或历史,执行搜寻

思路:关键是event,点选通过e.currentTarget.dataset.value拿到所点选的值,再交给其他方法执行搜寻行为。// 点选热门搜寻值或搜寻历史,填入搜寻框

fill_value:function(e){

let that = this;

console.log(history)

// console.log(e.currentTarget.dataset.value)

that.setData({

searchKey: e.currentTarget.dataset.value,//点选吧=把值给searchKey,让他去搜索

inputValue: e.currentTarget.dataset.value,//在输入框显示内容

showView:false,//给false值,隐藏 热搜和历史 界面

showsongresult: false, //给false值,隐藏搜寻建议页面

})

that.searchResult(); //执行搜寻功能

}

搜寻结果

思路:输入结束->确认键->呼叫searchResult请求到结果// 搜寻完成点选确认

searchover:function(){

let that = this;

that.setData({

showsongresult: false

})

that.searchResult();

}

// 搜寻结果

searchResult(){

console.log(this.data.searchKey)

API.searchResult({ keywords: this.data.searchKey, type: 1, limit: 100, offset:2 }).then(res => {

if (res.code === 200) {

this.setData({

searchresult: res.result.songs

})

}

})

}

乐库部分

乐库部分其实没什么逻辑很难的部分,以结构和样式为主,在这里就不赘述了。。在这里分享一些小功能的实现和踩到的坑。个性推荐,主播电台切换

1.个性推荐和主播电台是两个swiper-item所以他们才可以左右滑动,就像轮播图一样,不过轮播图放的是图片,而这里放的是整个页面。 2.我要实现的效果是左右滑动的同时,个性推荐和主播电台下面的白色方块也要跟着滑动。

1. 第一种方法

给包裹两个swiper-item的swiper新增一个bindchange="changeline"事件,把事件物件event打印出来发现,console.log(e.detail.current),当我们左右滑动的时候cuurrent的值会在0和1之间切换。所以我给白色方块新增class="{{changeline?'swiper_header_line_before':'swiper_header_line_after'}}"

if(e.detail.current === 0){

this.setData({

changeline:true

})

}else{

this.setData({

changeline:false

})

}

当current为0,即页面在个性推荐时,让changeline为true;当current为1,即页面在主播电台时,让changeline为false;为true时,给白色方块加持swiper_header_line_before的样式,为false时,加持swiper_header_line_after的样式。这样就可以跟随swiper-item的滑动而切换了。但是,这种切换方式太僵硬了,没有那种流畅的切换效果,而且不适合多swiper-item页面。

2. 第二种方法

让一半宽度,四分之一宽度设定为变数是为了相容不同的手机型号。因为写死资料肯定会有BUG,所以才要计算宽度。

.weui-navbar-slider{

width:28px;

height: 5px;

background: #ffffff;

border-radius:10rpx;

transition: transform .6s;

}

slideOffset为变数,动态接受从data传来的资料。

onLoad:function(){

wx.getSystemInfo({

success: function (res) {

// console.log(res.windowWidth)

// console.log(res.windowWidth / 2 / 2)

half = res.windowWidth / 2 ;

quarter = res.windowWidth / 2 / 2;

that.setData({

slideOffset: quarter - 14 //onLoad的时候让 quarter - 14 给slideOffset,即一开始就让他在个性推荐的下面,否则onLoad的时候一开始在0的位置

})

}

})

}

changeline:function(e){

// console.log(e)

// console.log(e.detail.current)

let current = e.detail.current; //获取swiper的current值

if(e.detail.current === 0){

this.setData({

slideOffset: quarter - 14

})

}

if(e.detail.current === 1){

this.setData({

slideOffset: (quarter - 14) + half

})

}

if(e.detail.current === null){

this.setData({

slideOffset: quarter - 14

})

}

}

MV播放

主要是结构和样式,我直接上程式码了。

show-center-play-btn="{{showcenterplaybtn}}" enable-progress-gesture="{{enableprogressgesture}}" show-mute-btn="{{showmutebtn}}" title="{{mv.name}}"

play-btn-position="{{center}}" object-fit="{{objectfit}}">

{{mv.name}}

发行: {{mv.publishTime}}

播放次数: {{mv.playCount}}

{{mv.desc}}

点赞: {{mv.likeCount}}

收藏: {{mv.subCount}}

评论: {{mv.commentCount}}

分享: {{mv.shareCount}}

/* play/play_mv.wxss */

.mv_box{

width: 100%;

height: 480rpx;

margin-top:-2rpx;

}

.mv{

width: 100%;

height: 100%;

border-radius:15rpx;

}

.mv_name{

margin-top:20rpx;

margin-left:20rpx;

}

.mv_time{

font-size: 12px;

margin-left:20rpx;

color:#979798;

display:initial;

}

.mv_times{

margin-left: 100rpx;

}

.mv_desc{

display: block;

color:#6A6B6C;

}

.mv_other{

display: block;

}

// play_mv.js

const API_BASE_URL = 'http://musicapi.leanapp.cn';

const app = getApp();

Page({

data: {

mv: [],

autoplay: true,

loop: true,

showfullscreenbtn: true,

showcenterplaybtn: true,

enableprogressgesture: true,

showmutebtn: true,

objectfit: 'contain',

},

onLoad: function (options) {

// console.log(mv_url);

const mvid = options.id; // onLoad()后获取到歌曲视讯之类的id

// 请求MV的地址,失败则播放出错,成功则传值给createBgAudio(后台播放管理器,让其后台播放)

wx.request({

url: API_BASE_URL + '/mv/detail',

data: {

mvid: mvid

},

success: res => {

console.log(res.data.data.brs['480'])

console.log('歌曲音讯url:', res)

if (res.data.data.brs === null) { //如果是MV 电台 广告 之类的就提示播放出错,并返回首页

console.log('播放出错')

wx.showModal({

content: '服务器开了点小差~~',

cancelColor: '#DE655C',

confirmColor: '#DE655C',

showCancel: false,

confirmText: '返回',

complete() {

wx.switchTab({

url: '/pages/index/index'

})

}

})

} else {

this.setData({

mv: res.data.data

})

}

}

})

},

})

歌手榜

// 歌手榜的js

const API = require('../../API/api');

const app = getApp();

Page({

data: {

songers: [], //歌手榜

},

onLoad: function (options) {

wx.showLoading({

title: '载入中',

});

this.getSonger();

},

getSonger: function () {

API.getSonger({}).then(res => {

wx.hideLoading()

this.setData({

songers: res.list.artists.slice(0, 100)

})

})

},

handleSheet: function (event) { //event 物件,自带,点选事件后触发,event有type,target,timeStamp,currentTarget属性

const sheetId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId

wx.navigateTo({ //获取到id带着完整url后跳转到play页面

url: `./moremore_songer?id=${sheetId}`

})

},

})

{{index + 1}}

{{item.name}}

{{item.score}}热度

// 歌手下级路由歌曲列表

const API_BASE_URL = 'http://musicapi.leanapp.cn';

const app = getApp();

Page({

data: {

songList: []

},

onLoad: function (options) {

wx.showLoading({

title: '载入中',

});

const sheetId = options.id;

wx.request({

url: API_BASE_URL + '/artists',

data: {

id: sheetId

},

success: res => {

const waitForPlay = new Array;

for (let i = 0; i waitForPlay.push(res.data.hotSongs[i].id) //循环push ID 到waitForPlay阵列

app.globalData.waitForPlaying = waitForPlay //让waitForPlay阵列给全域性阵列

// console.log(app.globalData.waitForPlaying)

}

wx.hideLoading()

console.log(res.data.hotSongs)

this.setData({

songList: res.data.hotSongs

})

}

})

},

handlePlayAudio: function (event) { //event 物件,自带,点选事件后触发,event有type,target,timeStamp,currentTarget属性

const audioId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId

wx.navigateTo({ //获取到id带着完整url后跳转到play页面

url: `../../play/play?id=${audioId}`

})

}

})

{{index + 1}}

{{item.name}}

{{item.ar[0].name}} - {{item.al.name}}

推荐歌单

因为样式与排行榜类似,所以只放出图片,源代码可以到我的github上检视。榜单排行

请检视源代码换一换功能

思路:系结点选事件->选取随机的三个数->给空值->push三个随机数进阵列中->重新赋值。 // 换一换

change_1:function(){

let maxNum = this.data.more_recommend_create.length //计算资料长度

let r1 = parseInt(Math.random() * (maxNum - 0) + 0); //取【0-资料长度】内的整数随机数

let r2 = parseInt(Math.random() * (maxNum - 0) + 0);

let r3 = parseInt(Math.random() * (maxNum - 0) + 0);

this.setData({

recommend_create: []

})

//重新取3组资料

this.data.recommend_create.push(this.data.more_recommend_create[r1])

this.data.recommend_create.push(this.data.more_recommend_create[r2])

this.data.recommend_create.push(this.data.more_recommend_create[r3])

//重新赋值

this.setData({

recommend_create: this.data.recommend_create

})

}

播放界面

图片太大,因此加快了播放。播放功能

思路:利用data-id="{{item.id}}"获取到歌曲ID放在event中-> 通过event物件事件获取ID并跳转到播放页面 ->wx.request获取到歌曲的音讯地址及detail->背景音讯管理器 wx.getBackgroundAudioManager()->播放

以歌手榜下级路由歌曲列表为例,

handlePlayAudio: function (event) { //event 物件,自带,点选事件后触发,event有type,target,timeStamp,currentTarget属性

const audioId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId

wx.navigateTo({ //获取到id带着完整url后跳转到play页面

url: `../../play/play?id=${audioId}`

})

}

// play.js

const API_BASE_URL = 'http://musicapi.leanapp.cn';

const app = getApp();

Page({

data: {

isPlay: '',

song:[],

innerAudioContext: {},

show:true,

showLyric:true,

songid:[],

history_songId:[]

},

onLoad: function (options) {

const audioid = options.id; // onLoad()后获取到歌曲视讯之类的id

this.play(audioid); //把从wxml获取到的值传给play()

},

play: function (audioid){

const audioId = audioid;

app.globalData.songId = audioId; //让每一个要播放的歌曲ID给全域性变数的songId

const innerAudioContext = wx.createInnerAudioContext();

this.setData({

innerAudioContext,

isPlay: true

})

// 请求歌曲音讯的地址,失败则播放出错,成功则传值给createBgAudio(后台播放管理器,让其后台播放)

wx.request({

url: API_BASE_URL + '/song/url',

data: {

id: audioId

},

success: res => {

if (res.data.data[0].url === null) { //如果是MV 电台 广告 之类的就提示播放出错,并返回首页

wx.showModal({

content: '服务器开了点小差~~',

cancelColor: '#DE655C',

confirmColor: '#DE655C',

showCancel: false,

confirmText: '返回',

complete() {

wx.switchTab({

url: '/pages/index/index'

})

}

})

} else {

this.createBgAudio(res.data.data[0]);

}

}

})

//获取到歌曲音讯,则显示出歌曲的名字,歌手的资讯,即获取歌曲详情;如果失败,则播放出错。

wx.request({

url: API_BASE_URL + '/song/detail',

data: {

ids: audioId //必选引数ids

},

success: res => {

if (res.data.songs.length === 0) {

wx.showModal({

content: '服务器开了点小差~~',

cancelColor: '#DE655C',

confirmColor: '#DE655C',

showCancel: false,

confirmText: '返回',

complete() {

wx.switchTab({

url: '/pages/index/index'

})

}

})

} else {

this.setData({

song: res.data.songs[0], //获取到歌曲的详细内容,传给song

})

app.globalData.songName = res.data.songs[0].name;

}

},

})

},

createBgAudio(res) {

const bgAudioManage = wx.getBackgroundAudioManager(); //获取全域性唯一的背景音讯管理器。并把它给例项bgAudioManage

app.globalData.bgAudioManage = bgAudioManage; //把例项bgAudioManage(背景音讯管理器) 给 全域性

bgAudioManage.title = 'title'; //把title 音讯标题 给例项

bgAudioManage.src = res.url; // res.url 在createBgAudio 为 mp3音讯 url为空,播放出错

const history_songId = this.data.history_songId

const historySong = {

id: app.globalData.songId,

songName:app.globalData.songName

}

history_songId.push(historySong)

bgAudioManage.onPlay(res => { // 监听背景音讯播放事件

this.setData({

isPlay: true,

history_songId

})

});

bgAudioManage.onEnded(() => { //监听背景音乐自然结束事件,结束后自动播放下一首。自然结束,呼叫go_lastSong()函式,即歌曲结束自动播放下一首歌

this.go_lastSong();

})

wx.setStorageSync('historyId', history_songId); //把historyId存入快取

},

})

暂停/播放

// 播放和暂停

handleToggleBGAudio() {

// const innerAudioContext = app.globalData.innerAudioContext;

const bgAudioManage = app.globalData.bgAudioManage;

const {isPlay} = this.data;

if (isPlay) {

bgAudioManage.pause();

// innerAudioContext.pause();handleToggleBGAudio

} else {

bgAudioManage.play();

// innerAudioContext.play();

}

this.setData({

isPlay: !isPlay

})

console.log(this.data.isPlay)

}

上一首/下一首(随机播放)

思路:点选歌单或歌手页,获取到对应的歌单/歌手id->wx.request请求资料获取到所有的歌单内/歌手热门歌曲音讯地址->给全域性变数globalData->点选上一首/下一首随机获取到全域性变数的一则资料->给play()方法->播放

onLoad: function (options) {

wx.showLoading({

title: '载入中',

});

const sheetId = options.id;

wx.request({

url: API_BASE_URL + '/playlist/detail',

data: {

id: sheetId

},

success: res => {

const waitForPlay = new Array;

for (let i = 0; i waitForPlay.push(res.data.playlist.trackIds[i].id) //循环push ID 到waitForPlay阵列

app.globalData.waitForPlaying = waitForPlay //让waitForPlay阵列给全域性阵列

}

wx.hideLoading()

this.setData({

songList: res.data.playlist.tracks

})

}

})

}

go_lastSong:function(){

let that = this;

const lastSongId = app.globalData.waitForPlaying;

const songId = lastSongId[Math.floor(Math.random() * lastSongId.length)]; //随机选取lastSongId阵列的一个元素

that.data.songid = songId;

this.play(songId)//传进play()方法中

app.globalData.songId=songId;

}

歌词/封面切换

因为网易云API的歌词界面崩溃,请求不到歌词,所以我只能把歌词写死为纯音乐,请欣赏。类似于v-show。

纯音乐,请欣赏

// 点选切换歌词和封面

showLyric(){

const {showLyric} = this.data;

this.setData({

showLyric: !showLyric

})

}

破产版的孤独星球动效

封面旋转:@keyframes rotate {

0%{

transform: rotate(0);

}

100%{

transform: rotate(360deg);

}

}

扩散的圆形线条: 其实就是外面套一个盒子,盒子边框变大以及透明度逐渐变低。

@keyframes moveCircle {

0%{

width: 400rpx;

height: 400rpx;

border: 1px solid rgba(255, 255, 255, 1)

}

30%{

width: 510rpx;

height: 510rpx;

border: 1px solid rgba(255, 255, 255, 0.8)

}

50%{

width: 610rpx;

height: 610rpx;

border: 1px solid rgba(255, 255, 255, 0.6)

}

80%{

width: 700rpx;

height: 700rpx;

border: 1px solid rgba(255, 255, 255, 0.4)

}

99%{

width: 375px;

height: 375px;

border: 1px solid rgba(255, 255, 255, 0.1)

}

100%{

width: 0px;

height: 0px;

border: 1px solid rgba(255, 255, 255, 0)

}

}

背景毛玻璃

复制程式码

/* 播放界面毛玻璃效果 */

.background_img{

position: fixed;

top: 0;

left: 0;

bottom: 0;

width: 100%;

height: 100%;

filter: blur(20px);

z-index: -1;

transform: scale(1.5); /*和网易云音乐对比了一下,发现也是放大1.5倍*/

}

复制程式码

播放tabBar

思路是参考酷狗音乐小程式。这个tabBar的js,wxml与播放功能界面的js,wxml相同。因为音乐播放是用wx.getBackgroundAudioManager()背景音讯播放器管理的,所以才能同步。

我的tabBar

播放历史

思路:play.js中一旦播放成功就把歌名及歌曲ID传入全域性变数->push到play.js里的阵列中->wx.setStorageSync把资料存入快取->在需要的页面wx.getStorageSync获取到快取。

const history_songId = this.data.history_songId

const historySong = {

// id: res.id

id: app.globalData.songId,

songName:app.globalData.songName

}

history_songId.push(historySong)

wx.setStorageSync('historyId', history_songId); //把historyId存入快取

onShow:function(){

var history = wx.getStorageSync('historyId');

// console.log(history)

this.setData({

hidden:true,

// historyId: app.globalData.songName

historyId: history

})

console.log(this.data.historyId)

}

博主GitHub:https://github.com/zhongjunhaoz/CloudMusic

文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 手机价格 笔记本电脑价格 小米手机价格 红米手机价格 耳机价格