用VUEJS做一個(gè)網(wǎng)易云音樂

用VUEJS做一個(gè)網(wǎng)易云音樂


技術(shù)棧


  • vue全家桶 (vue vue-router vuex)
  • axios
    http://www.reibang.com/p/8e5fb763c3d7?winzoom=1
  • Muse-UI(一個(gè)基于Vue2.x的material design 風(fēng)格UI框架)


功能與思路分析


我之前學(xué)習(xí)JS的時(shí)候?qū)tml5 audio研究過荒澡,也寫過一些例子,那時(shí)的功能并不是很全面与殃。在寫這個(gè)程序之前单山,我好好的查閱了當(dāng)前的HTML5中的audio標(biāo)簽碍现,發(fā)現(xiàn)園子上一位園友總結(jié)的很不錯(cuò)(這里)。于是就先把網(wǎng)易云音樂最基本的功能實(shí)現(xiàn)米奸,歌單部分(這也是我喜歡網(wǎng)易云音樂的原因這一)昼接,然后實(shí)現(xiàn)音樂的上一曲、下一曲悴晰,播放慢睡、暫停。列表功能铡溪。


后臺(tái)

后臺(tái)采用.net做為后臺(tái)提供系統(tǒng)請(qǐng)求所用的API(源碼)漂辐,原理很簡單就是用.net偽裝成一個(gè)客戶端去訪問網(wǎng)易云音樂的API然后,把返回的json數(shù)據(jù)轉(zhuǎn)發(fā)出來佃却。同時(shí)服務(wù)端做下跨域處理者吁。


核心代碼:

/// <summary>
/// 請(qǐng)求網(wǎng)易云音樂接口
/// </summary>
/// <typeparam name="T">要請(qǐng)求的接口類型</typeparam>
/// <param name="config">要請(qǐng)求的接口類型的對(duì)象</param>
/// <returns>請(qǐng)求結(jié)果(JSON)</returns>
public static string Request<T>(T config) where T : RequestData, new()
{
    // 請(qǐng)求URL
    string requestURL = config.Url;
    // 將數(shù)據(jù)包對(duì)象轉(zhuǎn)換成QueryString形式的字符串
    string @params = config.FormData.ParseQueryString();
    bool isPost = config.Method.Equals("post", StringComparison.CurrentCultureIgnoreCase);

    if (!isPost)
    {
        // get方式 拼接請(qǐng)求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");
    // 如果服務(wù)端啟用了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";            
    // 設(shè)置cookies
    req.Headers["Cookie"] = "appver=1.5.2";
    req.Method = config.Method;
    req.AutomaticDecompression = DecompressionMethods.GZip;
    if (isPost)
    {
        // 寫入post請(qǐng)求包
        byte[] formData = Encoding.UTF8.GetBytes(@params);
        // 設(shè)置HTTP請(qǐng)求頭  參考:https://github.com/darknessomi/musicbox/blob/master/NEMbox/api.py          
        req.GetRequestStream().Write(formData, 0, formData.Length);
    }            
    // 發(fā)送http請(qǐng)求 并讀取響應(yīng)內(nèi)容返回
    return new StreamReader(req.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8")).ReadToEnd();
}

vuejs部分


項(xiàng)目結(jié)構(gòu)

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API請(qǐng)求
├── components
│   ├── playBar.vue
│   └── ...
└── store
│    └── index.js        # 整個(gè)項(xiàng)目的vuex部分
└── router
│   └── router.js        # 整個(gè)項(xiàng)目的路由
└── utils                # 一些工具類模塊
│
└── views                # 項(xiàng)目中的一些route-view
說項(xiàng)目的路由之前,先來看一張效果圖

對(duì)于整個(gè)項(xiàng)目來說:視圖區(qū)別在于頂部導(dǎo)航灶泵,下面的bar的是否出來取決于育八,當(dāng)前系統(tǒng)列表中是否有歌曲,如果有就會(huì)出現(xiàn)赦邻。

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'
  }],
  // 讓每個(gè)頁面都滾動(dòng)到頂部髓棋,改變模式為mode: history
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }
})

vuex部分

這部分,主要是歌曲這一塊惶洲,因?yàn)椴煌捻撁嬗胁煌氖褂玫搅烁枨畔瓷寻堰@部分?jǐn)?shù)據(jù)放到vuex中做統(tǒng)一的數(shù)據(jù)處理!sotre/index.js

const store = new Vuex.Store({
  state: {
    audio: {
      'id': 0,
      'name': '歌曲名稱',
      'singer': '演唱者',
      'albumPic': '/static/player-bar.png',
      'location': '',
      'album': ''
    },
    lyric: '正在加載中恬吕。签则。',
    currentIndex: 0, // 當(dāng)前播放的歌曲位置
    playing: false, // 是否正在播放
    loading: false, // 是否正在加載中
    showDetail: false,
    songList: [],    // 播放列表
    currentTime: 0,
    tmpCurrentTime: 0,
    durationTime: 0,
    bufferedTime: 0,
    change: false   // 判斷是更改的時(shí)間還是播放的時(shí)間
  },
  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) { // 檢測(cè)歌曲重復(fù)
        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
    }
  },
  // 異步的數(shù)據(jù)操作
  actions: {
    getSong ({commit, state}, id) {
      commit('openLoading')
      Axios.get(api.getSong(id)).then(res => {
        // 統(tǒng)一數(shù)據(jù)模型,方便后臺(tái)接口的改變
        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)
        }
      })
    }
  }
})

最后上點(diǎn)項(xiàng)目截圖


github項(xiàng)目地址:https://github.com/javaSwing/NeteaseCloudWebApp
目前只完成app歌單部分柒凉,也是最核心的部分。這個(gè)項(xiàng)目會(huì)一直更新!如果覺的不錯(cuò)就給個(gè)star吧

Date: 2017-02-17

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末篓跛,一起剝皮案震驚了整個(gè)濱河市膝捞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愧沟,老刑警劉巖绑警,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件求泰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡计盒,警方通過查閱死者的電腦和手機(jī)渴频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來北启,“玉大人卜朗,你說我怎么就攤上這事」敬澹” “怎么了场钉?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懈涛。 經(jīng)常有香客問我逛万,道長,這世上最難降的妖魔是什么批钠? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任宇植,我火速辦了婚禮,結(jié)果婚禮上埋心,老公的妹妹穿的比我還像新娘指郁。我一直安慰自己,他們只是感情好拷呆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布闲坎。 她就那樣靜靜地躺著,像睡著了一般茬斧。 火紅的嫁衣襯著肌膚如雪腰懂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天项秉,我揣著相機(jī)與錄音绣溜,去河邊找鬼。 笑死伙狐,一個(gè)胖子當(dāng)著我的面吹牛涮毫,可吹牛的內(nèi)容都是我干的瞬欧。 我是一名探鬼主播贷屎,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼艘虎!你這毒婦竟也來了唉侄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤野建,失蹤者是張志新(化名)和其女友劉穎属划,沒想到半個(gè)月后恬叹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡同眯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绽昼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片须蜗。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硅确,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出明肮,到底是詐尸還是另有隱情菱农,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布柿估,位于F島的核電站循未,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏秫舌。R本人自食惡果不足惜的妖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舅巷。 院中可真熱鬧羔味,春花似錦、人聲如沸钠右。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽飒房。三九已至搁凸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狠毯,已是汗流浹背护糖。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嚼松,地道東北人嫡良。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像献酗,于是被迫代替她去往敵國和親寝受。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言:自己學(xué)習(xí)VUEJS也一段時(shí)間罕偎,但一直沒有做出來一東西很澄。我自己一直喜歡用網(wǎng)易云音樂app,于是乎就做了這個(gè)ap...
    極樂君閱讀 1,050評(píng)論 0 24
  • 傳自:皓令天下 煉己筑基 上陽子曰:“金丹之道,先須煉己甩苛,使神全氣盛也蹂楣,七情不動(dòng),五賊不亂讯蒲,六根天定痊土,精難動(dòng)搖,方...
    乘x風(fēng)閱讀 404評(píng)論 0 3
  • 昨天是農(nóng)歷十月初一墨林,是三大鬼節(jié)之一:寒食節(jié)施戴。 在這一天天黑前,大街兩邊就站滿了售賣各類用來給已收的人燒著用的衣服萌丈、...
    白衣秀士閱讀 314評(píng)論 0 0
  • 本文集不生產(chǎn)知識(shí)赞哗,只是知識(shí)的搬運(yùn)工。 演講是一種同步的辆雾、情感豐富的表達(dá)溝通工具肪笋;寫作是一種異步的、無損傳播的溝通工...
    胡楊Jessica閱讀 475評(píng)論 0 0
  • 剛過二十度迂,或者說剛過兩個(gè)十年藤乙。一個(gè)寫著懵懂,一個(gè)說著青澀惭墓。無論從何而看坛梁,都是一幅令人懷念的畫卷,像物理教研組前的兩...
    臣不依閱讀 414評(píng)論 3 4