项目部分截图(Gif)




前言

前一阵子学习了微信小程序,为了巩固所学的知识和提高实战经验,决定自己手撸一款小程序。因为听歌一直在用网易云音乐,所以突发奇想就做一款仿网易云音乐的小程序吧!开发中遇到了很多在学习中没有遇到过的坑,也很感谢在我改不出BUG时给予帮助的老师同学!本着学习和分享的目的,写下以下的文字,希望能给初学小程序的你带来一点帮助,大佬轻点喷。


关于项目的使用

本文的最后我贴出了项目的源码地址,需要的可以去GitHub上clone。完成后直接在微信开发者工具上打开就能使用。

开发前准备

  • 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四个文件。更多细节参考微信小程序文档

<!-- index.html --><!-- 自定义tabbar页面 -->
<cover-view class="tab-bar"><cover-view class="tab-bar-border"></cover-view><!--tabBal边框样式  -->
<!-- 乐库tabbar --><cover-view class='tab-bar-item' ><cover-image src='../images/music.png' hidden='{{isShow_index}}' bindtap='switchTab_index'></cover-image><cover-image src='../images/selected-music.png' hidden='{{!isShow_index}}' bindtap='switchTab_index'></cover-image><cover-view style="color:{{isShow_index ? selectedColor : color}}">乐库</cover-view></cover-view><!-- 播放tabbar --><cover-view class='tab-bar-item' bindtap='switchTab_playing'><cover-image src='../images/selected-playing.png' hidden='{{isShow_playing}}'></cover-image><cover-image src='../images/playing.png' hidden='{{!isShow_playing}}'></cover-image><cover-view></cover-view></cover-view><!-- 我的tabbar --><cover-view class='tab-bar-item' bindtap='switchTab_me'><cover-image src='../images/me.png' hidden='{{isShow_me}}'></cover-image><cover-image src='../images/selected-me.png' hidden='{{!isShow_me}}'></cover-image><cover-view style="color:{{isShow_me ? selectedColor : color}}">我的</cover-view></cover-view>
</cover-view>
// index.js
Component({data: {isShow_index:true,isShow_playing:false,isShow_me:false,selected: 0, //首页color: "#8D8D8D",selectedColor: "#C62F2F",list: [{pagePath: "/pages/index/index",iconPath: "/images/music.png",selectedIconPath: "/images/selected-music.png",text: "乐库"}, {pagePath: "/pages/love/love",iconPath: "/images/selected-playing.png",selectedIconPath: "/images/playing.png",text: ""},{pagePath: "/pages/me/me",iconPath: "/images/me.png",selectedIconPath: "/images/selected-me.png",text: "我的"}]},methods: {switchTab_index:function(){wx.switchTab({url:'/pages/index/index'})this.setData({isShow_index: true,isShow_me: false,isShow_playing: false})},switchTab_playing: function () {wx.switchTab({url: '/pages/love/love'})this.setData({isShow_playing: true,isShow_index: false,isShow_me: false})},switchTab_me: function () {wx.switchTab({url: '/pages/me/me'})this.setData({isShow_me:true,isShow_playing: false,isShow_index: false})}}
})

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来请求,但是这种方法只能请求一次数据,如果首页用wx.request来请求的话,代码看起来会很冗长和杂乱。不仅自己容易搞糊涂,其他人看代码时也会很累。因此为了代码的整洁干净,我在这里新建了一个文件专门存放API。一般在根目录下的utils文件夹下新建一个api.js,但我在根目录下新建了文件夹API,里面包含api.js

// 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,链接我找不到了,所以我放上了我的github上的weui.wxss
2.把下载好的weui.wxss放到根目录下。
3.在app.wxss@import "weui.wxss";引入一下就可以使用微信提供给我们的样式了。
4.WeUI样式库

热门搜索


上面已经提到我从api.js中拿数据。

 // 从接口到获取到数据导入到hotsongsgethotsongs() {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!=""){ //解决 如果输入框的值为空时,传值给搜索建议,会报错的bugthat.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})}})
}

乐库部分


乐库部分其实没什么逻辑很难的部分,以结构和样式为主,在这里就不赘述了。可以到我的github上查看。在这里分享一些小功能的实现和踩到的坑。

个性推荐,主播电台切换


1.个性推荐和主播电台是两个swiper-item所以他们才可以左右滑动,就像轮播图一样,不过轮播图放的是图片,而这里放的是整个页面。
2.我要实现的效果是左右滑动的同时,个性推荐主播电台下面的白色方块也要跟着滑动。
1. 第一种方法
给包裹两个swiper-itemswiper添加一个bindchange="changeline"事件,把事件对象event打印出来发现,console.log(e.detail.current),当我们左右滑动的时候cuurrent的值会在01之间切换。所以我给白色方块添加

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


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

<view class="weui-navbar-slider" style="transform:translateX({{slideOffset}}px);"></view>
.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播放


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

<!-- play_mv.wxml -->
<view class="mv_box"><video src="{{mv.brs['480']}}" class="mv" autoplay="{{autoplay}}" loop="{{loop}}" direction="{{0}}" show-fullscreen-btn="{{showfullscreenbtn}}"show-center-play-btn="{{showcenterplaybtn}}" enable-progress-gesture="{{enableprogressgesture}}" show-mute-btn="{{showmutebtn}}" title="{{mv.name}}"play-btn-position="{{center}}" object-fit="{{objectfit}}"></video>
</view><view class="mv_name">{{mv.name}}</view>
<view class="mv_time"> 发行:  {{mv.publishTime}}</view>
<view class="mv_time mv_times">播放次数:  {{mv.playCount}}</view>
<view class="mv_time mv_desc">{{mv.desc}}</view>
<view class="mv_time mv_desc mv_other">点赞: {{mv.likeCount}}</view>
<view class="mv_time mv_desc mv_other">收藏: {{mv.subCount}}</view>
<view class="mv_time mv_desc mv_other">评论: {{mv.commentCount}}</view>
<view class="mv_time mv_desc mv_other">分享: {{mv.shareCount}}</view>
/* 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赋值给audioIdwx.navigateTo({                                 //获取到id带着完整url后跳转到play页面url: `./moremore_songer?id=${sheetId}`})},
})
<!-- 歌手榜结构 -->
<view wx:for="{{songers}}" wx:key="" class='songer_box' data-id="{{item.id}}" bindtap='handleSheet'><view class='songer_index_box'><text class='songer_index'>{{index + 1}}</text></view><view class='songer_img_box'><image src="{{item.picUrl}}" class='songer_img'></image></view><view class='songer_name_box'><text class='songer_name'>{{item.name}}</text><text class='songer_score'>{{item.score}}热度</text></view>
</view>
// 歌手下级路由歌曲列表
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 <= res.data.hotSongs.length - 1; i++) { //循环打印出其idwaitForPlay.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赋值给audioIdwx.navigateTo({                                 //获取到id带着完整url后跳转到play页面url: `../../play/play?id=${audioId}`})}
})
<!-- more/more_songer/moremore_songer.wxml歌手下面的歌曲 -->
<view class='search_result_songs'><view wx:for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'><view class='songer_index_box'><text class='songer_index'>{{index + 1}}</text></view><view class='songer_img_box'><view class='search_result_song_song_name'>{{item.name}}</view><view class='search_result_song_song_art-album'>{{item.ar[0].name}} - {{item.al.name}}</view></view></view>
</view>

推荐歌单


因为样式与排行榜类似,所以只放出图片,源码可以到我的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()->播放
以歌手榜下级路由歌曲列表为例,

<view wx:for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'>
  handlePlayAudio: function (event) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性const audioId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioIdwx.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()后获取到歌曲视频之类的idthis.play(audioid); //把从wxml获取到的值传给play()
},
play: function (audioid){const audioId = audioid;app.globalData.songId = audioId;  //让每一个要播放的歌曲ID给全局变量的songIdconst 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(); //获取全局唯一的背景音频管理器。并把它给实例bgAudioManageapp.globalData.bgAudioManage = bgAudioManage;         //把实例bgAudioManage(背景音频管理器) 给 全局bgAudioManage.title = 'title';                        //把title 音频标题 给实例bgAudioManage.src = res.url;                          // res.url 在createBgAudio 为 mp3音频  url为空,播放出错const history_songId = this.data.history_songIdconst 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存入缓存
},
})

暂停/播放

  <!-- 暂停播放图标 -->
<view class="play_suspend"><view class="icon_playing"><image bindtap="handleToggleBGAudio" src="../images/suspend.png" hidden="{{!isPlay}}" class="{{'img_play_suspend'}}" />  <!-- 暂停图标--><image bindtap="handleToggleBGAudio" src="../images/play.png" hidden="{{isPlay}}" class="{{'img_play_suspend'}}" /></view> <!--播放图标-->
</view>
// 播放和暂停
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 <= res.data.playlist.trackIds.length - 1;i++){ //循环打印出其idwaitForPlay.push(res.data.playlist.trackIds[i].id) //循环push ID 到waitForPlay数组app.globalData.waitForPlaying = waitForPlay  //让waitForPlay数组给全局数组}wx.hideLoading()this.setData({songList: res.data.playlist.tracks})  }})
}
<view class="icon_playing "><image src="../images/lastSong.png" class=" icon_play" bindtap="go_lastSong" /></view>
<view class="icon_playing "><image src="../images/nextSong.png" class=" icon_play" bindtap="go_lastSong" /></view>
  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

 <!-- 封面 -->
<!-- 一开始onload时,showLyric=true, 显示为转动的图标,点击图标,切换为歌词-->
<view class="sing-show" bindtap="showLyric" ><view class="moveCircle {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}"><image src="{{song.al.picUrl}}" class="coverImg {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}"/></view><text  hidden="{{showLyric}}" class="songLyric">纯音乐,请欣赏</text>
</view>
  // 点击切换歌词和封面
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)}
}

背景毛玻璃

<!-- play.wxml -->
<image src="{{song.al.picUrl}}" class="background_img" ></image>
/* 播放界面毛玻璃效果 */
.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获取到缓存。

<!--play.js-->
const history_songId = this.data.history_songId
const historySong = {// id: res.idid: app.globalData.songId,songName:app.globalData.songName}history_songId.push(historySong)wx.setStorageSync('historyId', history_songId); //把historyId存入缓存
<!--me.js-->onShow:function(){var history = wx.getStorageSync('historyId');// console.log(history)this.setData({hidden:true,//  historyId: app.globalData.songNamehistoryId: history})console.log(this.data.historyId)}

结语

做项目的过程总的来说痛并快乐,因为改不出BUG的样子真的很狼狈,但实现了某一个功能的那一刻真的很欣慰。再次感谢给予帮助的老师同学。如果你喜欢这篇文章或者可以帮到你,不妨点个赞吧!同时也非常希望看到这篇文章的你在下方给出建议!

源码

本项目源码

仿网易云音乐(微信小程序版)相关推荐

  1. 仿网易云音乐微信小程序源码

    欢迎star issue 仿网易云音乐APP的微信小程序 源码下载地址,自己去下载就行,http://code.662p.com/view/19352.html 目前实现功能 用户 歌单 FM 播放 ...

  2. 网易云音乐微信小程序 毕业设计

    本期我们将讲解网易云音乐微信小程序. 之前我已经讲过网易云音乐小程序的旧版本.现在是全新的版本,涉及的功能点非常多.接下来,我们将对这个项目进行详细介绍. 首先,我们来看整个网易云音乐小程序的主页,也 ...

  3. 尚硅谷微信小程序开发 仿网易云音乐App 小程序 后端接口服务器搭建

    目录 小程序学习 视频相关的教程文档与笔记分享 配套服务器 源码地址: 接口使用说明文档 接口列表 启动服务 测试服务启动OK网页 http://localhost:3000/test.html​编辑 ...

  4. 仿网易云音乐的小程序项目(粗糙版)

    前言 通过github拉取下的后台服务器,根据接口文档,调用真实的网易云音乐数据,独立开发而成,收获很多 完成了 : 每日推荐的歌曲,歌单,视频页面,账号登陆的展示数据信息功能 歌曲和视频的播放切换, ...

  5. [干货教程]仿网易云课堂微信小程序开发实战经验

    本篇文章想跟大家分享下:我们公司"湖北诚万兴科技"最近刚帮客户定制开发.目前已上线的"哎咆课堂"微信小程序的开发经验分享.首先大概介绍下这个小程序所涉及到的主要 ...

  6. 计算机毕业设计Node.js+Vue安卓仿网易云音乐客户端APP(程序+源码+LW+部署)

    该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程.欢迎交流 项目运行 环境配置: Node.js+ Vscode + Mysql5.7 + HBuilderX+Navicat11+Vue ...

  7. 微信小程序仿网易云音乐(使用云开发,提供源码)

    微信小程序仿网易云音乐(使用云开发,提供源码)!!!!!!!!!!! 源码: 链接:https://pan.baidu.com/s/1z_ZnRVbT4vjEENimi8yBQQ 提取码:u0o3 一 ...

  8. 新版仿网易云音乐的YY音乐微信小程序源码

    正文: 仿网易云音乐的YY音乐微信小程序源码,这是一款仿网易云手机端的小程序源码 名为YY音乐小程序,首页有每日推荐歌曲.歌单和热歌排行榜,功能不是特别多 但是还是比较简洁美观的,省去了现在网易云音乐 ...

  9. 高仿网易云音乐UI的微信小程序源码

    简介: 仿网易云音乐的微信小程序源码,名为YY音乐小程序,首页有每日推荐歌曲.歌单和热歌排行榜,功能不是特别多但是该有的都有. UI特别精致,简直就是克隆的网易云音乐. 源码下载:百度网盘自取 图片:

最新文章

  1. lvs服务器需要开启web服务么_如何检测 Web 服务请求丢失问题
  2. npm 卸载_手把手教你创建一个NPM包
  3. ARM汇编:乘法指令集
  4. python3 写文件时指定文件名_Python3基础 os listdir 列举指定的所有文件及文件夹的名字...
  5. 线程----BlockingQueue (转)
  6. 集成学习之sklearn中的xgboost基本用法 (转载)
  7. 微型计算机惠普1hm20av,微型计算机原理及应用(答案).doc
  8. 类似索引Model套Model之 iOS模型闲聊二
  9. Android 样式
  10. 面试中的智力题及编程实践(二)
  11. 【VRP】基于matlab禁忌搜索算法求解初始点和终点确定的取送货路径问题【含Matlab源码 1224期】
  12. 《居里夫人自传》的读后感作文1700字
  13. python输出10行带标号的hello、world_#000 Python 入门第一题通过扩展,学到了更多的知识...
  14. java bfs 迷宫例子_51-迷宫(一)- java版dfs和bfs
  15. python爬虫从入门到放弃,含案例分析,超详细讲解(一)
  16. 同济大学计算机科学与技术系,同济大学计算机科学与技术系简介_跨考网
  17. 「Arm Arch」 ISA 概述
  18. java整合消息推送_SpringMVC整合websocket实现消息推送及触发功能
  19. 用ArcMap打开MXD文件报One or more layers failed to draw错误!
  20. 数据科学与大数据技术和大数据管理与应用哪个好

热门文章

  1. AB1756-L71固件下载(1)
  2. 太阳能电池基本特性实验报告_太阳能电池IV特性实验报告
  3. 安装ROS-Academy-for-Beginners教学包
  4. 如何修复烧写镜像文件失败的SD卡
  5. 【日本雅虎新闻推荐】:Embedding-based News Recommendation for Millions of Users(附开源代码)
  6. 日文vista系统改成中文vista系统的一点经验
  7. 牛客多校补题专场 Monotonic Matrix LGV定理
  8. C语言stu1成员age的非法引用,2009年上海市高校计算机等级考试(2级C-b卷)
  9. 【原创意】新浪微博都感到巨大鸭梨的全新创意 —— 二维码社区神码
  10. 夜神模拟器如何连接电脑WiFi