沐鳴怎麼當代理?_用 Vue.js 模仿一個簡單的網易雲音樂

前言:自己學習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吧