用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