仿网易云音乐微信小程序

前言

前段闲暇时间想了解一下网易云音乐的加密方式,想写个具体实现,flutter我放弃了,还是小程序简单点,即衍生出该项目,纯原生书写,仿安卓V6.25版本。网易云音乐加密方式稍后更新。

开发前的准备

  • VScode代码编辑器。
  • 微信开发者工具
  • Android网易云音乐(V6.25版本)
  • 网易云音乐API(node版本,这可能是github上最全的了,手动点赞)
  • 本人复写Java版本个别接口(若需)
  • 阿里巴巴矢量图标库
注:项目中http请求都是使用wx.request且未进行API封装,不是很优雅,有兴趣可自行封装

已实现功能及展示截图(GIF)
一、首页
首页
项目中主要使用弹性盒子布局

首页主要加入了scroll-view滑动区分四个模块,目前只实现其中一个

首页数据有(若需电台、MV数据可自行添加):

  • banner 轮播图
  • 推荐歌单
  • 推荐新音乐
  • 新碟
    /**
     * 获取轮播图数据
     */
    getBanner() {
        let that = this;
        wx.request({
            url: baseUrl + 'banner',
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                // console.log(res);
                if (res.data.code == 200) {
                    that.setData({
                        banner: res.data.banners
                    })
                }
            }
        })
    },

    /**
     * 获取歌单信息
     */
    getPersonalized() {
        let that = this;
        wx.request({
            url: baseUrl + 'personalized?limit=6',
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                if (res.data.code == 200) {
                    let pageData = res.data.result;
                    // 播放量四舍五入精确到万
                    pageData.forEach(function (item, index) {
                        item.playCount = (item.playCount / 10000).toFixed(0)
                    })
                    that.setData({
                        personalized: pageData
                    })

                }
            }
        })
    },

    /**
     * 推荐新音乐
     */
    getNewsong() {
        let that = this;
        wx.request({
            url: baseUrl + 'personalized/newsong',
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                // console.log(res.data.result);
                if (res.data.code == 200) {
                    that.setData({
                        newsong: res.data.result
                    })
                }
            }
        })
    },

    /**
     * 获取新碟信息
     */
    getNewest() {
        let that = this;
        wx.request({
            url: baseUrl + 'album/newest',
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                // console.log(res);
                if (res.data.code == 200) {
                    that.setData({
                        newest: res.data.albums
                    })
                }
            }
        })
    },
wxml页面渲染即可,具体实现详见page/index

二、搜索

搜索

这一块实现也是花费了不少时间(手动吐槽)
(1)数据项比较多,样式需要细调
(2)scroll-view超出屏幕宽度之外的滑动(写完才发现尴尬问题)
(3)swiper高度问题,这玩意太烦了,导致onReachBottom事件只触发一次,最后偷鸡解决了

功能详解
  1. 搜索建议
    给input绑定实时监测输入框数据项(bindinput="inputext"),不为空时发送http请求
    searchSuggest() {
        let that = this;
        wx.request({
            url: baseUrl + 'search/suggest?keywords=' + this.data.searchKey + "&type=mobile",
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                // console.log(res)
                if (res.data.code == 200) {
                    that.setData({
                        searchsuggest: res.data.result.allMatch
                    })
                }
            }
        })
    },
  1. 搜索历史
    当用户点击搜索、搜索历史、热搜榜数据进行搜索时将数据存储到StorageSync中
      if (key != '') {
            let history = wx.getStorageSync("history") || [];
            if (history.includes(key)) {
                for (var i = 0; i < history.length; i++) {
                    if (key == history[i]) {
                        history.splice(i, 1);
                    }
                }
            }
            history.push(key)
            wx.setStorageSync("history", history);
        }
  1. 热搜榜(请求渲染即可)
    getSearchHotDetail() {
        let that = this;
        wx.request({
            url: baseUrl + 'search/hot/detail',
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                wx.hideLoading()
                // console.log(res.data);
                if (res.data.code == 200) {
                    that.setData({
                        searchHotDetail: res.data.data
                    })
                }
            }
        })
    },
  1. 搜索事件:详见searhFinput方法

  2. 上划加载更多
    swiper高度问题,导致至加载一次,后改用scroll-view的bindscrolltolower="loadMore"来实现

具体实现详见page/search

三、歌单

歌单

这块还是页面书写为注,毕竟非专业前端,比较费劲,具体请求数据有


   // 全部歌单
  gplaylist: function (isadd) {
    //分类歌单列表
    var that = this;
    // console.log(that.data.catelist.checked.name)
    wx.request({
      url: baseUrl + 'top/playlist',
      data: {
        limit: that.data.playlist.limit,
        offset: that.data.playlist.offset,
        cat: that.data.catelist.checked.name
      },
      complete: function (res) {
        // console.log(res)
        that.data.playlist.loading = true;
        if (!isadd) {
          that.data.playlist.list = res.data
        } else {
          res.data.playlists = that.data.playlist.list.playlists.concat(res.data.playlists);
          that.data.playlist.list = res.data
        }
        that.data.playlist.offset += res.data.playlists.length;
        that.setData({
          loading: false,
          playlist: that.data.playlist
        })
      }
    })
  },

  // 歌单选择
  cateselect: function (e) {
    var t = e.currentTarget.dataset.catype;
    this.data.catelist.checked = t
    this.setData({
      playlist: {
        list: {},
        offset: 0,
        limit: 30
      },
      loading: true,
      cateisShow: !this.data.cateisShow,
      catelist: this.data.catelist
    });
    this.gplaylist();
  }, 

  // 数据初始化
  init: function () {
    var that = this
    wx.request({
      url: baseUrl + 'playlist/catlist',
      complete: function (res) {
        that.setData({
          catelist: {
            isShow: false,
            res: res.data,
            checked: res.data.all
          }
        })
      }
    })
  },
具体实现详见page/gedanSquare

四、排行榜

排行榜

排行榜类型我是根据name来分类的(略微尴尬),榜单较多,就不一个个找id了

    /**
   * 所有榜单内容摘要
   */
  getToplistDetail() {
    let that = this;
    wx.request({
      url: baseUrl + 'toplist/detail',
      header: {
        'Content-Type': 'application/json'
      },
      success: function(res) {
        if (res.data.code == 200) {
          var list = res.data.list;
          var officialList = []
          var recommendationList = []
          var globalList = []
          var moreList = []
          for (var index in list) {
            var name = list[index].name
            if (name == "云音乐飙升榜" || name == "云音乐新歌榜" || name == "网易原创歌曲榜" || name == "云音乐热歌榜") {
              officialList.push(list[index])
            } else if (name == "江小白YOLO云音乐说唱榜" || name == "说唱TOP榜" || name == "云音乐电音榜" || name == "云音乐ACG音乐榜" || name == "云音乐欧美新歌榜" || name == "抖音排行榜") {
              recommendationList.push(list[index])
            } else if (name == "美国Billboard周榜" || name == "UK排行榜周榜" || name == "Beatport全球电子舞曲榜" || name == "日本Oricon周榜" || name == "iTunes榜" || name == "香港电台中文歌曲龙虎榜") {
              globalList.push(list[index])
            } else {
              moreList.push(list[index])
            }
          }
          that.setData({
            officialList,
            recommendationList,
            globalList,
            moreList,
            loading: false,
          })
        }
      }
    })
  },
此处有一个地方需要注意:

查看排行榜详情中,node接口作者将id简化做了一层转意,某些榜单无法查看详情
解决方案,使用本人覆写Java版本,点击事件改为openTopList即可



具体实现详见page/rankingList

五、新歌速递

新歌速递

调用接口渲染即可,数据较多,小程序渲染有卡顿,小伙伴们自行优化



书写仓促代码未优化(回头看重复代码有点多)
为了播放正常,此处数据项需要做一致性处理(没办法,先写的歌单播放,返回数据格式不同)


具体实现详见page/newsong

六、MV

mv

这边是区分两类数据的歌手的MV及用户上传的视频需要注意


    /**
     * 获取MV数据
     */
    getMvDetail(id) {
        let that = this;

        wx.request({
            url: baseUrl + 'mv/detail?mvid=' + id,
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                // console.log(res);
                if (res.data.code == 200) {
                    that.setData({
                        mvDetail: res.data.data,
                        mvId: id
                    })
                }
            }
        })
    },

    /**
     * 获取video数据
     */
    getVideoDetail(id) {
        let that = this;
        wx.request({
            url: baseUrl + 'video/detail?id=' + id,
            header: {
                'Content-Type': 'application/json'
            },
            success: function (res) {
                if (res.data.code == 200) {
                    that.setData({
                        'mvDetail.cover': res.data.data.coverUrl,
                        'mvDetail.name': res.data.data.title,
                        'mvDetail.publishTime': util.formatTimeCommit(res.data.data.publishTime, 3),
                        'mvDetail.desc': res.data.data.description,
                        'mvDetail.playCount': res.data.data.playTime,
                        'mvDetail.likeCount': res.data.data.praisedCount,
                        'mvDetail.subCount': res.data.data.subscribeCount,
                        'mvDetail.commentCount': res.data.data.commentCount,
                        'mvDetail.shareCount': res.data.data.shareCount,
                        mvId: id
                    })
                }
            }
        })
    },
具体实现详见page/mv

七、评论


(1)分歌单评论及歌曲评论,注意区分请求
(2)评论中emoji需要转换才能显示,详见具体方法


具体实现详见page/comment
  getPlaylistComment(id) {
    console.log("歌单评论:" + id)
    let that = this;
    wx.request({
      url: baseUrl + 'comment/playlist?id=' + id,
      header: {
        'Content-Type': 'application/json'
      },
      success: function (res) {
        // 评论表情与日期需要转换(!!!)
        var data = res.data;
        for (let i in data.hotComments) {
          data.hotComments[i].time = util.formatTimeCommit(data.hotComments[i].time, 2);
          data.hotComments[i].content = util.emoji(data.hotComments[i].content)
          if (data.hotComments[i].beReplied[0]) {
            data.hotComments[i].beReplied[0].content = util.emoji(data.hotComments[i].beReplied[0].content)
          }
        }
        for (let i in data.comments) {
          data.comments[i].time = util.formatTimeCommit(data.comments[i].time, 2);
          data.comments[i].content = util.emoji(data.comments[i].content)
          if (data.comments[i].beReplied[0]) {
            data.comments[i].beReplied[0].content = util.emoji(data.comments[i].beReplied[0].content)
          }
        };
        that.setData({
          hotComments: data.hotComments,
          comments: data.comments,
          total: data.total,
          loading: false
        })

        wx.setNavigationBarTitle({
          title: '评论(' + (data.total || 0) + ")"
        })
      }
    })
  },

具体实现详见page/comment

八、歌曲播放

歌曲播放

项目核心功能,写(抄)的不是很优秀,自行领悟

里面用到了微信小程序-通知广播WxNotificationCenter,别漏掉了!!!

具体实现详见page/player

九、播放栏


播放栏使用了模板template,在项目中template/pageplay中有具体样式
在需要的地方引入即可,别忘了通知广播,否则不能同步状态




目前只在首页及歌单页面添加了,如有需要请参照歌单页面方式进行添加。


写在最后

非专业前端,css可能不是很优雅,时间有限,小程序未进行优化。
本人第一个较完整微信小程序,耗时较长,再加上工作比较繁忙,所以暂不打算更新了,感兴趣的小伙伴自行完善。

数据源至
node版本:https://github.com/Binaryify/NeteaseCloudMusicApi
Java版本:本人覆写移步至https://www.jianshu.com/p/bb9ed6ef41b6

小程序参考
https://github.com/sqaiyan/NeteaseMusicWxMiniApp
https://github.com/zhongjunhaoz/CloudMusic

最后奉上源码wx-neteaseCloudMusic如果此项目对你有所帮助,麻烦给star吧。感谢!!!

本项目仅供学习参考,侵权删!!!