項目碼雲地址:https://gitee.com/Snow28/MyMusic(如果覺得可以,麻煩給我鼓勵一個star)
這個是基於Vue2.0,vuex,vue-router,axios和html5的flexible box佈局與及css3的transform,animation,transition組成。
技術棧
- vue
- vue-router
- vuex
- axios
- jsonp
- better-scroll
- good-storage
主要功能:
- 歌曲播放(從搜索歷史添加到播放列表,展示最近播放)
- 根據歌曲進度展示歌詞,提供進度條
- 歌曲收藏
- 個人頁面:顯示個人的最近播放和個人收藏
- 排行榜單頁面: 幾種榜單,詳情頁顯示排行數字
- 歌曲搜索(按人名,按歌名,刪除搜索歷史,無法搜索到歌曲頁面): 搜索框監聽內容變化顯示搜索結果,搜索結果上拉加載,點擊搜索結果添加到當前播放列表並播放該歌曲,搜索爲歌手跳轉歌手詳情頁;保存搜索歷史,顯示熱門搜索標籤
- 熱門歌單列表頁面
- 歌手頁面:按姓氏首字母排列,點擊側面字母欄跳轉到對應歌手區域
- 推薦頁面:推薦歌單
- 播放頁面: 旋轉大頭像,播放時間,進度條,切換歌曲(上一首,下一首) ,收藏,播放模式(單曲-順序-隨機),歌詞頁,播放按鈕,迷你底部播放欄(播放頁收起時顯示)
文件目錄:
項目是由vue-cli腳手架搭建的,
關於src文件中源代碼的內容:
api處理:
項目數據全是線上抓取的,在qq音樂的官網可以抓取文件的url和相應的請求參數。
在抓取數據過程中,遇到了跨域問題,ajax並不能實現跨域,於用到了jsonp技術。在common文件夾下封裝了jsonp的請求方法,
export default function jsonp(url, data, option) {
url += (url.indexOf('?') < 0 ? '?' : '&') + param(data)
return new Promise((resolve, reject) => {
originJsonp(url, option, (err, data) => {
if (!err) {
resolve(data)
} else {
reject(err)
}
})
})
}
export function param(data) {
let url = ''
for (var k in data) {
let value = data[k] !== undefined ? data[k] : ''
url += '&' + k + '=' + encodeURIComponent(value)
}
return url ? url.substring(1) : ''
}
處理了url的拼接和轉碼。
在base文件中,寫的是在很多地方相同的功能組件,例如:搜索框組件,搜索列表主鍵,滾動功能組件,歌去列表展示組件,tab導航組件。
在comon文件中,存放了靜態的字體文件,圖片文件,stylus樣式文件,還有一些封裝好功能的js組件(如:數據緩存功能,還有不能jsonp請求的接口,需要用vue中proxy做代理的,還有在多個vue組件中都會用到的方法寫在mixin.js文件中)
components文件就是我們開發主要功能目錄
router文件中定義一個index.js文件,在裏面管理路由的跳轉
store文件中主要負責vuex代碼,定義數據的state狀態,修改並相應數據狀態
main.js入口文件代碼如下所示:
在入口文件引入了vue-router路由,store的數據存儲狀態,vuex全局狀態管理,fastclick(避免移動端點擊延遲300ms的效果)以及vue-lazyload懶加載模塊。
vue-router引入頁面路由,對頁面組件進行導航,其中其他引入大多是自定義的頁面組件:
store文件中:
關於vuex全局保存各個組件共享的數據與及改變數據的邏輯,全部保存在store的index.js裏面,這是關鍵文件。包括利用數組模擬隊列進行播放歌曲的保存,搜索歷史的保存等關鍵邏輯。
App.vue組件
是整個項目的根目錄組件,在這裏掛載其他所有的組件。m-header是QQ音樂的頭部,tab組件是導航欄部分。router-view是將點擊tab單項匹配到路由組件加載到這塊內容。player就是音樂控制面板,當音樂詳情關閉,則就以小化的面板控制音樂。
vuex集中狀態管理 :
- 搜索歷史,收藏列表,播放歷史
- 播放狀態,播放模式,收起播放頁,當前播放索引
- 排行榜數據,推薦歌單數據,歌手數據(進入詳情頁使用)
完成該項目的難點:
數據抓取
存儲本地數據和瀏覽器數據的業務邏輯,頁面刷新後,數據狀態不會改變
組件的複用
better-scroll 移動端插件(每次dom渲染要重新計算scroll寬度),封裝成組件。
vuex狀態管理的項目結構設計
幾種歷史記錄收藏列表存儲在localStorage
1.1 解決Vue動態路由參數變化,頁面數據不更新
問題描述:
遇到動態路由如:/page/:id
從/page/1 切換到 /page/2 發現頁面組件沒有更新
解決方式:
給<router-view :key="key">增加一個不同:key值,這樣vue就會識別這是不同的了。
<router-view :key="key"></router-view>
...
computed:{
key(){
return this.$route.path + Math.random();
}
}
1.2 vue組件裏定時器銷燬問題
問題描述:
在a頁面寫一個定時器,每秒鐘打印一次,然後跳轉到b頁面,此時可以看到,定時器依然在執行。
推薦的解決方式:
通過$once這個事件偵聽器器在定義完定時器之後的位置來清除定時器。
const timer = setInterval(() => {
// 定時器操作
}, 1000)
// 通過$once來監聽定時器,在beforeDestroy鉤子可以被清除。
this.$once('hook:beforeDestroy', () => {
clearInterval(timer);
})
1.3 vue實現按需加載組件的兩種方式
1.使用resolve => require(['./ComponentA'], resolve),方法如下:
const ComponentA = resolve => require(['./ComponentA'], resolve)
- 使用 () => import(), 具體代碼如下:
const ComponentA = () => import('./ComponentA')
1.4 組件之間,父子組件之間的通信方案
組件之間的通信方案:
- 通過事件總線(bus),即通過發佈訂閱的方式
- vuex
- 父子組件:
- 父組件通過prop向自組件傳遞數據
- 子組件綁定自定義事件,通過this.$emit(event,params) 來調用自定義事件
- 使用vue提供的$parent/$children & $refs方法來通信
- provide/inject
- 深層次組件間的通信 $attrs, $listeners
實現細節可參考:https://m.jb51.net/article/167304.htm
1.5 vue中 $event 的用法--獲取當前父元素,子元素,兄弟元素
<button @click = “fun($event)”>點擊</button>
...
methods: {
fun(e) {
// e.target 是你當前點擊的元素
// e.currentTarget 是你綁定事件的元素
#獲得點擊元素的前一個元素
e.currentTarget.previousElementSibling.innerHTML
#獲得點擊元素的第一個子元素
e.currentTarget.firstElementChild
# 獲得點擊元素的下一個元素
e.currentTarget.nextElementSibling
# 獲得點擊元素中id爲string的元素
e.currentTarget.getElementById("string")
# 獲得點擊元素的string屬性
e.currentTarget.getAttributeNode('string')
# 獲得點擊元素的父級元素
e.currentTarget.parentElement
# 獲得點擊元素的前一個元素的第一個子元素的HTML值
e.currentTarget.previousElementSibling.firstElementChild.innerHTML
}
},