用VUEJS(2.x)做一个网易云音乐

作者: 前端er | 来源:发表于2017-02-16 15:48 被阅读1003次

    前言:自己学习VUEJS也一段时间,但一直没有做出来一东西。我自己一直喜欢用网易云音乐app,于是乎就做了这个app。

    技术栈

    • vue全家桶 (vue vue-router vuex)
    • axios
    • Muse-UI(一个基于Vue2.x的material design 风格UI框架)

    功能与思路分析

    我之前学习JS的时候对Html5 audio研究过,也写过一些例子,那时的功能并不是很全面。在写这个程序之前,我好好的查阅了当前的HTML5中的audio标签,发现园子上一位园友总结的很不错(这里)。于是就先把网易云音乐最基本的功能实现,歌单部分(这也是我喜欢网易云音乐的原因这一),然后实现音乐的上一曲、下一曲,播放、暂停。列表功能。

    后台

    后台采用.net做为后台提供系统请求所用的API(源码),原理很简单就是用.net伪装成一个客户端去访问网易云音乐的API然后,把返回的json数据转发出来。同时服务端做下跨域处理。

    核心代码:

    /// <summary>
    /// 请求网易云音乐接口
    /// </summary>
    /// <typeparam name="T">要请求的接口类型</typeparam>
    /// <param name="config">要请求的接口类型的对象</param>
    /// <returns>请求结果(JSON)</returns>
    public static string Request<T>(T config) where T : RequestData, new()
    {
        // 请求URL
        string requestURL = config.Url;
        // 将数据包对象转换成QueryString形式的字符串
        string @params = config.FormData.ParseQueryString();
        bool isPost = config.Method.Equals("post", StringComparison.CurrentCultureIgnoreCase);
    
        if (!isPost)
        {
            // get方式 拼接请求url
            string sep = requestURL.Contains('?') ? "&" : "?";
            requestURL += sep + @params;
        }
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestURL);
        req.Accept = "*/*";
        req.Headers.Add("Accept-Language", "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4");
        // 如果服务端启用了GZIP,那么下面必须解压,否则一直乱码。
        // 参见:http://www.crifan.com/set_accept_encoding_header_to_gzip_deflate_return_messy_code/
        req.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
        req.ContentType = "application/x-www-form-urlencoded";
        req.KeepAlive = true;
        req.Host = "music.163.com";
        req.Referer = "http://music.163.com/search/";
        req.UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537";            
        // 设置cookies
        req.Headers["Cookie"] = "appver=1.5.2";
        req.Method = config.Method;
        req.AutomaticDecompression = DecompressionMethods.GZip;
        if (isPost)
        {
            // 写入post请求包
            byte[] formData = Encoding.UTF8.GetBytes(@params);
            // 设置HTTP请求头  参考:https://github.com/darknessomi/musicbox/blob/master/NEMbox/api.py          
            req.GetRequestStream().Write(formData, 0, formData.Length);
        }            
        // 发送http请求 并读取响应内容返回
        return new StreamReader(req.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8")).ReadToEnd();
    }
    

    vuejs部分

    项目结构

    ├── index.html
    ├── main.js
    ├── api
    │   └── ... # 抽取出API请求
    ├── components
    │   ├── playBar.vue
    │   └── ...
    └── store
    │    └── index.js        # 整个项目的vuex部分
    └── router
    │   └── router.js        # 整个项目的路由
    └── utils                # 一些工具类模块
    │
    └── views                # 项目中的一些route-view
    

    说项目的路由之前,先来看一张效果图

    对于整个项目来说:视图区别在于顶部导航,下面的bar的是否出来取决于,当前系统列表中是否有歌曲,如果有就会出现。

    router.js核心部分

    const router = new VueRouter({
      mode: 'history',
      routes: [{
        path: '/index',
        component: require('../views/index'),
        children: [
          {
            path: 'rage',
            component: require('../views/rage')
          },
          {
            path: 'songList',
            component: require('../views/songList')
          },
          {
            path: 'leaderBoard',
            component: require('../views/leaderBoard')
          },
          {
            path: 'hotSinger',
            component: require('../views/hotSinger')
          }
        ]
      }, {
        name: 'playerDetail',
        path: '/playerDetail/:id',
        component: require('../views/playerDetail')
      }, {
        path: '/playListDetail/:id',
        name: 'playListDetail',
        component: require('../views/playListDetail')
      }, {
        path: '*', redirect: '/index/rage'
      }],
      // 让每个页面都滚动到顶部,改变模式为mode: history
      scrollBehavior (to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition
        } else {
          return { x: 0, y: 0 }
        }
      }
    })
    

    vuex部分

    这部分,主要是歌曲这一块,因为不同的页面有不同的使用到了歌曲信息,把把这部分数据放到vuex中做统一的数据处理!
    sotre/index.js

    const store = new Vuex.Store({
      state: {
        audio: {
          'id': 0,
          'name': '歌曲名称',
          'singer': '演唱者',
          'albumPic': '/static/player-bar.png',
          'location': '',
          'album': ''
        },
        lyric: '正在加载中。。',
        currentIndex: 0, // 当前播放的歌曲位置
        playing: false, // 是否正在播放
        loading: false, // 是否正在加载中
        showDetail: false,
        songList: [],    // 播放列表
        currentTime: 0,
        tmpCurrentTime: 0,
        durationTime: 0,
        bufferedTime: 0,
        change: false   // 判断是更改的时间还是播放的时间
      },
      getters: {
        audio: state => state.audio,
        playing: state => state.playing,
        loading: state => state.loading,
        showDetail: state => state.showDetail,
        durationTime: state => state.durationTime,
        currentIndex: state => state.currentIndex,
        bufferedTime: state => state.bufferedTime,
        tmpCurrentTime: state => state.tmpCurrentTime,
        songList: state => state.songList,
        change: state => state.change,
        currentTime: state => state.currentTime,
        prCurrentTime: state => {
          return state.currentTime / state.durationTime * 100
        },
        prBufferedTime: state => {
          return state.bufferedTime / state.durationTime * 100
        }
      },
      mutations: {
        play (state) {
          state.playing = true
        },
        pause (state) {
          state.playing = false
        },
        toggleDetail (state) {
          state.showDetail = !state.showDetail
        },
        setAudio (state) {
          state.audio = state.songList[state.currentIndex - 1]
        },
        setAudioIndex (state, index) {
          state.audio = state.songList[index]
          state.currentIndex = index + 1
        },
        removeAudio (state, index) {
          state.songList.splice(index, 1)
          state.audio = state.songList[index - 1]
          state.currentIndex = state.currentIndex - 1
          if (state.songList.length === 0) {
            state.audio = {
              'id': 0,
              'name': '歌曲名称',
              'singer': '演唱者',
              'albumPic': '/static/player-bar.png',
              'location': '',
              'album': ''
            }
            state.playing = false
          }
        },
        setChange (state, flag) {
          state.change = flag
        },
        setLocation (state, location) {
          state.audio.location = location
        },
        updateCurrentTime (state, time) {
          state.currentTime = time
        },
        updateDurationTime (state, time) {
          state.durationTime = time
        },
        updateBufferedTime (state, time) {
          state.bufferedTime = time
        },
        changeTime (state, time) {
          state.tmpCurrentTime = time
        },
        openLoading (state) {
          state.loading = true
        },
        closeLoading (state) {
          state.loading = false
        },
        resetAudio (state) {
          state.currentTime = 0
        },
        playNext (state) { // 播放下一曲
          state.currentIndex++
          if (state.currentIndex > state.songList.length) {
            state.currentIndex = 1
          }
          state.audio = state.songList[state.currentIndex - 1]
        },
        playPrev (state) { // 播放上一曲
          state.currentIndex--
          if (state.currentIndex < 1) {
            state.currentIndex = state.songList.length
          }
          state.audio = state.songList[state.currentIndex - 1]
        },
        addToList (state, item) {
          var flag = false
          state.songList.forEach(function (element, index) { // 检测歌曲重复
            if (element.id === item.id) {
              flag = true
              state.currentIndex = index + 1
            }
          })
          if (!flag) {
            state.songList.push(item)
            state.currentIndex = state.songList.length
          }
        },
        setLrc (state, lrc) {
          state.lyric = lrc
        }
      },
      // 异步的数据操作
      actions: {
        getSong ({commit, state}, id) {
          commit('openLoading')
          Axios.get(api.getSong(id)).then(res => {
            // 统一数据模型,方便后台接口的改变
            var url = res.data.data[0].url
            commit('setAudio')
            commit('setLocation', url)
          })
        },
        getLrc ({commit, state}, id) {
          commit('setLrc', '[txt](加载中。。。')
          Axios.get(api.getLrc(id)).then(res => {
            // 1、先判断是否有歌词
            if (res.data.nolyric) {
              commit('setLrc', '[txt](⊙0⊙) 暂无歌词')
            } else {
              console.log(res.data.lrc.lyric)
              commit('setLrc', res.data.lrc.lyric)
            }
          })
        }
      }
    })
    

    最后上点项目截图

    github项目地址:https://github.com/javaSwing/NeteaseCloudWebApp

    目前只完成app歌单部分,也是最核心的部分。这个项目会一直更新!如果觉的不错就给个star吧

    相关文章

      网友评论

      本文标题:用VUEJS(2.x)做一个网易云音乐

      本文链接:https://www.haomeiwen.com/subject/celhwttx.html