Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Jacquelyn38
• 阅读 1591

自己平时喜欢钻研技术,做项目。所以有幸发现了网易云音乐Nodejs版的API。网址如下:

https://binaryify.github.io/NeteaseCloudMusicApi/#/

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

打开网址就可以进入如上所示的网站,文档写的不错,非常容易上手。所以之前利用自己的服务器搭建了一个API服务,使用Vue.js快速搭建一个App应用。这个App应用是之前做的,之前一直没有分享给大家,这里我将开放源码,大家可以拉取下代码一起学习。以下内容是我对项目的介绍,源码在文末。

一、App视图

这里我挑出几张代表性的图片供大家参考。

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

二、资源

  • 前端框架:Vue.js
  • 后端框架:Node.js

  • UI框架:Muse ui

三、开放源码

文章暂列出部分源码,详情源码可以去github上拉取我的代码。代码是之前写的,可能还不够完善,大家可以照着我的思路继续完善下去,打造一个属于自己的的音乐App,以下代码为歌曲播放页。

<!--歌曲播放-->
<template>
  <div class="cc">
    <mu-circular-progress :size="40" class="icon" v-if="isloading"/>
    <div v-show="!isloading">
      <mu-appbar style="width: 100%;" class="back-b" color="primary">
        <mu-button icon slot="left" @click="back">
          <mu-icon value="arrow_back"></mu-icon>
        </mu-button>
        <div class="big">
          <marquee :lists="fullname"></marquee>
        </div>
        <mu-button icon slot="right"  @click="goindex">
          <mu-icon size="28" value="home" class="play" color="white" center ></mu-icon>
        </mu-button>
        <mu-button icon slot="right"  @click="open = !open">
          <mu-icon size="28" value="chat" class="play" color="white" center ></mu-icon>
        </mu-button>
        <mu-button icon slot="right" @click="love" v-show="love1===0">
          <mu-icon size="28" value="favorite_border" class="play" color="white" center ></mu-icon>
        </mu-button>
        <mu-button icon slot="right" @click="nolove" v-show="love1===1">
          <mu-icon size="28" value="favorite" class="play" color="white" center ></mu-icon>
        </mu-button>
        <mu-button icon slot="right"  @click="gomv">
          <mu-icon size="28" value="play_circle_outline" class="play" color="white" center></mu-icon>
        </mu-button>
      </mu-appbar>
      <div class="img-b" v-show="curId===0" @click="l1">
        <div class="img-n">
          <img :src="imgsrc" alt class="annim" >
        </div>
      </div>
      <div class="gc-box"  v-show="curId===1"  ref="gcbox"></div>
      <div class="gc-bb" v-show="curId===1"  @click="l0">
        <div class="gc-b"  >
          <div
            v-for="(item, index) in afterLrc"
            :id="'line-'+index"
            :key="index"
            class="gc"
            ref="gc1"
          >{{item.txt}}</div>
        </div>
      </div>
      <mu-drawer :open.sync="open" :docked="docked" :right="position === 'right'">
        <mu-load-more @refresh="refresh" :refreshing="refreshing" :loading="loading" @load="load">
          <div class="pl-box">
            <div v-for="item in songpl" :key='item.index' class="ovf pl">
              <div class="pl-l"><img :src="item.user.avatarUrl" alt=""></div>
              <div class="pl-r">
                <div class="name">{{item.user.nickname}}</div>
                <div class="time">{{item.time|getdate}}</div>
                <div class="con">{{item.content}}</div>
              </div>
            </div>
          </div>
        </mu-load-more>
      </mu-drawer>
    </div>
  </div>

</template>

<script>import marquee from './marquee'
import Bus from '../bus/bus'
import {mapGetters} from 'vuex'
export default {
  name: 'song',
  components: {
    marquee
  },
  data () {
    return {
      curId: 0,
      isloading: false,
      imgsrc: '',
      lyric: '',
      love1: 0,
      lrcIndex: 0,
      afterLrc: [],
      arra: [],
      lrcTime: [],
      name: '',
      currentTime1: '',
      ppxx: '',
      arrtime: [],
      i: 0,
      currentLine: 0,
      songname: '',
      mvid: '',
      subname: '',
      songpl: [],
      docked: false,
      open: false,
      position: 'left',
      num: 20,
      refreshing: false,
      loading: false,
      open1: false,
      ids: '',
      ff: '',
      backpath: '',
      isserch: false,
      fullname: ''
    }
  },
  computed: {
    ...mapGetters({
      getsong: 'getsong',
      getplaylist: 'getplaylist',
      getid: 'getid',
      getxh: 'getxh',
      getname: 'getname',
      getcs: 'getcs',
      geti: 'geti'
    })
  },
  watch: {},
  methods: {
    goindex () {
      this.$router.replace({
        name: 'index'
      })
    },
    // 下一首
    next1 () {
      if (this.$store.state.xh >= this.getplaylist.length - 1) {
        this.$store.commit('backzero')
        this.$router.replace({
          name: 'song',
          params: {
            id: this.getplaylist[this.getxh].id,
            name1: this.getplaylist[this.getxh].name,
            sub: this.getplaylist[this.getxh].sub
          }
        })
      } else {
        this.$store.commit('add')
        this.$router.replace({
          name: 'song',
          params: {
            id: this.getplaylist[this.getxh].id,
            name1: this.getplaylist[this.getxh].name,
            sub: this.getplaylist[this.getxh].sub
          }
        })
      }
    },
    // 上一首
    prep () {
      if (this.$store.state.xh > 0) {
        this.$store.commit('jian')
        this.$router.replace({
          name: 'song',
          params: {
            id: this.getplaylist[this.getxh].id,
            name1: this.getplaylist[this.getxh].name,
            sub: this.getplaylist[this.getxh].sub
          }
        })
      } else {
        this.$store.state.xh = this.getplaylist.length - 1
        this.$router.replace({
          name: 'song',
          params: {
            id: this.getplaylist[this.getxh].id,
            name1: this.getplaylist[this.getxh].name,
            sub: this.getplaylist[this.getxh].sub
          }
        })
      }
    },
    // 播放结束
    ender () {
      this.next1()
    },
    // 评论
    refresh () {
      this.refreshing = true
      this.$refs.container.scrollTop = 0
      setTimeout(() => {
        this.refreshing = false
      }, 500)
    },
    // 评论触底刷新
    load () {
      this.loading = true
      setTimeout(() => {
        this.loading = false
        this.num += 20
        this.$axios
          .get([
            '/api/comment/music?id=' + this.$route.params.id + '&limit=' + this.num
          ])
          .then(response => {
            // success
            this.songpl = response.data.comments
          })
          .catch(error => {
            // error
            alert('失败!')
            console.log(error)
          })
          .catch(error => {
            // error
            alert('失败')
            console.log(error)
          })
      }, 500)
    },
    l1 () {
      this.curId = 1
    },
    l0 () {
      this.curId = 0
    },
    // 歌词滚动
    timeupdate (data) {
      // data = 0
      // console.log(data)
      this.currentLine = 0
      this.currentTime1 = data
      // document.querySelector('audio').currentTime = 0
      for (let j = this.currentLine; j < this.arrtime.length; j++) {
        if (
          this.currentTime1 < this.arrtime[j + 1] &&
          this.currentTime1 > this.arrtime[j]
        ) {
          this.currentLine = j
          this.ppxx = 260 - this.currentLine * 32
          document.querySelector('.gc-b').style.transform = 'translateY(' + this.ppxx + 'px)'
          for (let i = 0; i < this.$refs.gc1.length; i++) {
            this.$refs.gc1[i].className = 'gc'
          }
          this.$refs.gc1[this.currentLine].className = 'on'
          break
        }
      }
    },
    // 快进
    seeked (data) {
      if (document.querySelector('.gc-b').innerHTML === '暂无歌词') {
        return false
      } else {
        this.currentTime1 = data
        for (let i = 0; i < this.$refs.gc1.length; i++) {
          this.$refs.gc1[i].className = 'gc'
        }
        for (let k = 0; k < this.arrtime.length; k++) {
          if (
            this.currentTime1 < this.arrtime[k + 1] &&
            this.currentTime1 < this.arrtime[k]
          ) {
            this.currentLine = k
            break
          }
        }
      }
    },
    // 图片开始暂停
    pause1 () {
      document.querySelector('img').classList.add('pause11')
      this.ff = 1
    },
    // 图片开始转动
    play1 (e) {
      document.querySelector('img').classList.remove('pause11')
      document.querySelector('img').classList.add('annim')
    },
    // 喜欢
    love () {
      this.love1 = 1
      this.$axios
        .get([
          '/api/like?id=' + this.$route.params.id
        ])
        .then(response => {
          // success
        })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
    },
    // 不喜欢
    nolove () {
      this.love1 = 0
      this.$axios
        .get([
          '/api/like/?like=false&id=' + this.$route.params.id
        ])
        .then(response => {
          // success
        })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
    },
    // mv
    gomv () {
      document.querySelector('audio').pause()
      this.$router.push({
        name: 'mv',
        params: {
          id: this.mvid
        }
      })
    },
    // 获取信息
    get () {
      this.$store.state.iid = this.$route.params.id
      this.fullname = this.$route.params.name1 + '——' + this.$route.params.sub
      // mvid
      this.isloading = true
      this.$axios.get(['/api/search?keywords=' + this.$route.params.name1 + '&type=1004']).then(response => {
        // success
        this.mvid = response.data.result.mvs[0].id
      })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
      // 歌曲的url
      this.$axios
        .get([
          '/api/song/url?id=' + this.$route.params.id
        ])
        .then(response => {
          // success
          this.$store.commit('song', response.data.data[0].url)
        })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
      // 歌曲的id
      this.$axios
        .get([
          '/api/song/detail?ids=' + this.$route.params.id
        ])
        .then(response => {
          // success
          this.$refs.gcbox.style.background = 'url(' + response.data.songs[0].al.picUrl + ')'
          this.imgsrc = response.data.songs[0].al.picUrl
          this.name = response.data.songs[0].name
          this.$store.state.name = response.data.songs[0].name
          this.subname = response.data.songs[0].ar[0].name
          this.songname = response.data.songs[0].name
        })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
        .catch(error => {
          // error
          alert('失败')
          console.log(error)
        })
      // 歌曲评论
      this.$axios
        .get([
          '/api/comment/music?id=' + this.$route.params.id + '&limit=' + this.num
        ])
        .then(response => {
          // success
          this.songpl = response.data.comments
        })
        .catch(error => {
          // error
          alert('失败!')
          console.log(error)
        })
        .catch(error => {
          // error
          alert('失败')
          console.log(error)
        })
      // 歌词
      this.$axios
        .get(['/api/lyric?id=' + this.$route.params.id])
        .then(response => {
          // success
          if (response.data.lrc !== undefined) {
            this.lyric = response.data.lrc.lyric
          }
          if (response.data.lrc === undefined) {
            document.querySelector('.gc-b').innerHTML = '暂无歌词'
          }
          // 格式处理
          var lyrics = this.lyric.split('\n')
          var lrcObj = []
          /*eslint-disable */
          var timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g;
          for (var i = 0; i < lyrics.length; i++) {
            var timeRegExpArr = lyrics[i].match(timeReg)
            if (!timeRegExpArr) continue;
            var txt = lyrics[i].replace(timeReg, "")
            // 处理时间
            for (var k = 0; k < timeRegExpArr.length; k++) {
              var array = {}
              var t = timeRegExpArr[k]
              /*eslint-disable */
              var min = Number(String(t.match(/\[\d*/i)).slice(1))
              var sec = Number(String(t.match(/\:\d*/i)).slice(1))
              var time = min * 60 + sec
              array.time = time;
              array.txt = txt;
              lrcObj.push(array);
            }
            this.afterLrc = lrcObj;
          }
          for (let i = 0; i < this.afterLrc.length; i++) {
            var element = this.afterLrc[i].time;
            this.arrtime.push(element);
          }
          this.arrtime[this.arrtime.length] = this.arrtime[this.arrtime.length - 1] + 3
          this.isloading = false;
        })
        .catch(error => {
          //error
          alert("失败!");
          console.log(error);
        });
      this.isloading = false
    },
    // 返回
    back() {
      this.$router.go(-1)
      this.$router.isBack = true
    }
  },
  created () {
    this.get()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'get'
  },
  // 一开始加载
  mounted () {
    Bus.$on('timeupdate1', (data) => {
      this.timeupdate(data)
    })
    Bus.$on('seeked1', (data) => {
      this.seeked(data)
    })
    Bus.$on('pause11', () => {
      this.pause1()
    })
    Bus.$on('play11', () => {
      this.play1()
    })
    Bus.$on('prep1', () => {
      this.prep()
    })
    Bus.$on('next1', () => {
      this.next1()
    })
    Bus.$on('ender', () => {
      this.ender()
    })
    if (this.$store.state.i === 'dj' ) {
      this.$store.state.songs = []
      this.$store.state.i = ''
    }
    if (!this.isserch) {
      var arr = this.getplaylist
      arr.forEach((value, index) => {
        if (Number(arr[index].id) === Number(this.$route.params.id)) {
          this.$store.commit('xh', index)
        }
      })
    }
    this.$store.state.isshow=true
    this.$store.state.cs = 0
    this.$store.state.states = ''
  },
  // 时间处理
  filters: {
    getdate (val) {
      let date = new Date(val)
      let year = date.getFullYear()
      let month = date.getMonth() + 1
      let dates = date.getDate()
      let hour = date.getHours()
      let min = date.getMinutes()
      let second = date.getSeconds()
      if (month < 10) {
        month = '0' + month
      } if (dates < 10) {
        dates = '0' + dates
      } if (hour < 10) {
        hour = '0' + hour
      } if (min < 10) {
        min = '0' + min
      } if (second < 10) {
        second = '0' + second
      }
      let box = year + '-' + month + '-' + dates + ' ' + hour + ':' + min + ':' + second
      return box
    }
  },
  beforeRouteEnter(to, from, next) {
    next(vm => {
        if (from.name === 'search') {
          // vm.$store.commit('subbottom', 'pld')
          vm.$store.state.isshow = true
          vm.$store.commit('playlist', '')
          vm.isserch = true
        }
    })
  },
  // 销毁之前
  beforeDestroy () {
    Bus.$off('timeupdate1')
    Bus.$off('seeked1')
    Bus.$off('pause11')
    Bus.$off('play11')
    Bus.$off('next1')
    Bus.$off('prep1')
    Bus.$off('ender', this.ender)
  },
  // 路由离开
  beforeRouteLeave (to, from, next) {
    this.$store.state.isshow = false
    this.$store.commit('name', this.name)
    this.$store.commit('subname', this.subname)
    this.$store.commit('src', this.imgsrc)
    next();
  },
};
</script>
<style  scoped>
  @keyframes rotating {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
  .cc{
    background: rgba(0, 0, 0, 0.2);
  }
  .big{
    width:60%;
    float: left;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
  }
  .tab{
    position: fixed;
    bottom:65px;
    width: 100%;
    max-width: 640px;
    background: #fff !important;
  }
  .tab-b div:nth-child(1){
    float: left;
    width: 50%;
    text-align: center;
  }
  .play{
    float: right;
  }
  .tab-b div:nth-child(2){
    float: right;
    width: 50%;
    text-align: center;
  }
  .name {
    font-size:14px;
    font-weight: bold;
  }
  .icon {
    display: block !important;
    margin: 75% auto 0;
  }
  .img-b {
    width: 100%;
    margin: 0 auto;
    padding: 24.5vh 0;
    text-align: center;
    position: relative;
    line-height: 0;
    z-index: 10;
    background: rgba(192,192,192,0.1);
    background: url('../assets/images/player-needle.png') no-repeat center 6%;
    background-size: 27%;
  }
  .img-n{
    width: 250px;
    height: 250px;
    z-index: 9;
    border-radius: 100%;
    margin: 0 auto;
    background: url("../assets/images/record-bg.png") no-repeat center center;
    background-size: 100%;
    box-shadow: 0px 0px 10px  #DCDCDC;
  }
  .img-b img {
    width: 170px;
    height: 170px;
    margin: 40px 0 ;
    border-radius: 100%;
    box-shadow: 0px 0px 15px  #DCDCDC;
  }
  .annim{
    animation: rotating 10s infinite linear;
  }
  .pause11{
    animation-play-state:paused;
  }
  .play1{
    margin-left:5%;
  }
  .back-b {
    position: fixed;
    z-index: 100;
    max-width: 640px;
    width: 100%;
    top: 0;
  }
  .gc {
    text-align: center;
    font-size: 16px;
    height: 32px;
    line-height:32px;
  }
  .gc-box {
    height:600px;
    margin-top:56px;
    position: relative;
    overflow: hidden;
    background-size: cover!important;
    background-repeat: no-repeat!important;
    background-position:center;
    filter: blur(5px);
  }
  .gc-bb{
    z-index: 1;
    width: 100%;
    height:600px;
    position: fixed;
    top:7%;
    background: rgba(0, 0, 0, 0.2);
  }
  .gc-b {
    height: 520px;
    transform: translateY(260px);
    text-align: center;
    color: #fff;
    transition-duration: 600ms;
  }
  .on {
    text-align: center;
    font-size: 16px;
    color: #2196f3 !important;
    height: 32px;
    line-height:32px;
  }
  .ppl-t{
    font-size: 14px;
    color: #666666;
    margin-left:20px ;
  }
  .back-b{
    position: fixed;
    top: 0;
    width: 100%;
    max-width: 640px;
    z-index: 100;
  }
  .ttt{
    font-size: 18px;
  }
  .video-b{
    width: 100%;
    background: #fff;
    z-index: 100;
    margin-top:15% ;
  }
  .video1{
    width: 100%;
    height: auto;
  }
  .pl{
    width: 95%;
    margin: 10px auto;
    padding: 10px 0;
    border-bottom:1px solid #eaeaea ;
  }
  .pl-box{
    overflow: auto;
  }
  .pl:last-child{
    border:none;
  }
  .pl-l{
    width: 20%;
    float: left;
  }
  .pl-l img{
    width: 60%;
    border-radius: 50%;
    margin-top:10px;
  }
  .pl-r{
    width: 78%;
    float: left;
  }
  .time{
    width: 100%;
    color: #999999;
    font-size:14px;
    line-height: 24px;
  }
  .name{
    font-size:14px;
    color: #333;
  }
  .con{
    width: 100%;
    color: #333333;
    font-size:15px;
  }
</style>

四、源码地址

以下就是源码,大家可以拉取一下参考我的思路,自己实现一个属于自己的音乐App哦!

https://github.com/maomincoding/ZM-music3k
  • 欢迎关注我的公众号前端历劫之路

  • 回复关键词电子书,即可获取12本前端热门电子书。

  • 回复关键词红宝书第4版,即可获取最新《JavaScript高级程序设计》(第四版)电子书。

  • 关注公众号后,点击下方菜单即可加我微信,我拉拢了很多IT大佬,创建了一个技术交流、文章分享群,期待你的加入。

  • 作者:Vam的金豆之路

  • 微信公众号:前端历劫之路

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)

本文转转自微信公众号前端历劫之路原创https://mp.weixin.qq.com/s/OJ_7j4icCgGT1FK297dMpA,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这