這是一個仿網易雲音樂PC端的網頁應用,基於Vue + Element UI 構建,整體頁面樣式都比較簡潔,網頁主體被設計為類似Windows桌面下打開一個窗口app一樣,應用的主體就是一個窗口,可以通過拖曳右下角來改變視窗大小,儘管作為一個web應用它被設計成這樣可能有點奇怪,但這並不是不可行的,或許以後還能衍生出web桌面,類似於雲端桌面的感覺。
好像是個挺不錯的想法,或許以後可是試著搭建一個這樣的web桌面,提供一個基礎平台管理每個窗口的生命週期,再基於這個平台開發web應用, 將自己的web應用都放上去。
專案後端來自網易雲音樂NodeJS 版API 以及該專案的完整介面文檔
該專案的介面文檔頁面已經無法存取了,我產生了一份離線文檔,你可以從這裡下載
專案目前還有部分頁面沒有完成,不過主要頁面都已完成,並將持續更新專案部署於我的網易雲音樂(仿)
由於伺服器為境內伺服器,而網域解析到境內主機又需要備案, 又因為我剛好因為沒有居住證的原因過不了備案,所以只能用ip直接存取了
這部分將介紹如何讓這個專案正常運作
$ git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git
$ npm install
伺服器啟動預設連接埠為3000,若不想使用3000 連接埠,可使用以下指令: windows
$ set PORT=4000
Mac/Linux 下
$ PORT=4000
$ cd NeteaseCloudMusicApi
$ node app.js
$ git clone https://github.com/ColorlessWin/cloud_music.git
$ npm install
專案預設伺服器位址為http://localhost
連接埠為3000
, 如果需要修改則在本專案的根目錄下新建一個.env.local
文件,寫下如下鍵值對即可
VUE_APP_HOST=/*这里填你的服务器地址(需要加http或https前缀)*/
VUE_APP_PORT=/*这里填你的服务器端口*/
/**
* 示例
* VUE_APP_HOST=https://webservices.fun
* VUE_APP_PORT=80
*/
$ npm run serve
$ npm run build
本專案包含一個自己寫的webpack插件,他的功能是在build完成後自動將建置好的檔案上傳至伺服器,不過因為
.env.local
檔案配置的原因它只有在我的電腦上建置時才能正確的找到伺服器並上傳文件,所以它在你的電腦上build時會報錯,但這並不會影響到專案的構建
僅僅只是在本地運行的話,所有配置保持預設就好了
這部分將向你介紹專案中的一個核心元件<Rendering/>
,專案中的大量頁面中都使用到了這個元件,了解這個元件的工作方式是了解本專案大部分原始碼的重要途徑。
<Rendering/>
組件是一個負責渲染項目中所有其可以被抽象為Array<Object>
格式的數據,該項目中有著大量的這樣的數據,比如歌曲列表、歌手列表、專輯列表、評論列表等等一切符合Array<Object>
格式的資料。而
<Rendering/>
元件也會接管這些資料的加載,分頁處理等等,你要做的事情很簡單只需要實作一個filling
方法並透過props傳遞給<Rendering/>
元件
我們將透過專案中的一個簡單的頁面來介紹這個元件。
這是一個MV分類頁面,透過切換不同的分類標籤,頁面將向你展示相對應的MV列表,底部還有一個簡單的分頁功能。 讓我們看看是怎麼使用<Rendering/>
方便的實現這些功能的
你可以先體驗這個頁面
底部的分頁
我們再看看這個頁面原始碼部分的大致結構
< template >
< span >地区:</ span >
< simple-radio :options = " areaLabel " v-model = " area " /> < br >
< span >类型:</ span >
< simple-radio :options = " typeLabel " v-model = " type " /> < br >
< span >排序:</ span >
< simple-radio :options = " orderLabel " v-model = " order " /> < br >
< rendering
class = " mvs "
:component = " require('@/components/content/matrices/CommonVideoMatrices').default "
:adapter = " adapter "
:show-creator = " true "
:total = " total "
:filling = " filling "
:unique = " area + type + order "
/>
</ template >
< script >
import ...
export default {
name : " Mv " ,
components : {LArea, Rendering, SimpleRadio},
data () {
return {
total : - 1 ,
area : '全部' ,
type : '全部' ,
order : '上升最快' ,
areaLabel : [ '全部' , '内地' , '港台' , '欧美' , '日本' ],
typeLabel : [ '全部' , '官方版' , '原声' , '现场版' , '网易出品' ],
orderLabel : [ '上升最快' , '最热' , '最新' ],
adapter : { ... }
}
},
methods : {
filling ( offset , limit , first_load ) { ... }
}
}
</ script >
這裡折疊掉了一些暫時不需要關注的內容, 完整源碼請看這裡
可以看到頁面中的template部分還是比較簡潔的,首先是3個<simple-radio/>
組件它們的功能很簡單, 透過data
中定義的三個Label數組渲染出對應的標籤,並在標籤被點擊後來透過v-model
更新對應的被綁定的屬性, 然後是一個<rendering/>
元件,上面綁定了許多prop
<rendering/>
組件細節看起來<rendering/>
有很多prop啊,其實不然, <rendering/>
只有2個prop, 其他的prop都會被傳遞給其內部的<component/>
和<pagination/>
< template >
< div >
< component
:is = " component "
v-bind = " Object.assign(props, $attrs) "
v-on = " $listeners "
/>
< pagination
v-model = " props.datas "
v-on = " $listeners "
v-bind = " $attrs "
:filling = " filling "
/>
</ div >
</ template >
< script >
import Pagination from " @/components/common/Pagination " ;
export default {
name : " Rendering " ,
components : {Pagination},
props : {
component : { type : [ Object , Function ], required : true },
filling : { type : Function , required : true },
},
data () {
return {
props : {
datas : [],
}
}
}
}
</ script >
<Rendering/>
原始碼片段,這裡刪掉了一些不需要關注的內容, 完整原始碼請看這裡
<pagination/>
是一個分頁元件,它負責渲染一個分頁元件提供互交的同時也負責管理資料的載入處理<component/>
則負責載入你透過component
這個prop傳遞進來的元件,在這個MV的頁面中我透過require([path]).default
的方式動態的將一個CommonVideoMatrices
元件傳遞給了component
而且可以看到我透過v-on="$listeners"
將CommonVideoMatrices
內部的事件代理了出去,這意味著你可以直接在<rendering/>
上監聽到CommonVideoMatrices
內部$emit
的事件
CommonVideoMatrices
是負責渲染一個實際的MV展示列表,他是在這個頁面中真正負責展示資料的, 其內部接受一個datas
的prop,(datas
應該始終是一個Array<Object>
格式的資料) 並透過datas
來渲染頁面專案中有著不少與
CommonVideoMatrices
設計類似的元件他們都透過一個datas
的prop渲染各自的資料,<rendering/>
內也只能傳入一個包含datas
prop的元件這些元件分別位於src/cmoponents/content/tracks
和src/component/content/matrices
下
<Pagination/>
會在頁面上渲染一個分頁元件用於提供互交只有在你提供了
total
這個prop的時候才會渲染這個分頁元件,否則不渲染,但仍可以管理資料的載入了解<Pagination/>
的更多細節可以查看源碼
上面介紹了<Rendering/>
元件的內部結構與部分細節, 至少我們知道了透過component
這個prop我們可以傳遞一個包含datas
prop的元件進去<Rendering/>
會幫我們渲染這個元件, 可是誰來給這個元件的datas
prop傳遞資料呢,透過什麼方式呢?
這就要提到<Rendering/>
元件內的另一個prop filling
了
與其他prop不同, filling
你需要傳遞一個function給他, 這個function將被用於數據的加載, 在需要時它會被自動調用,並且要求其返回一個Promise。
我們可以看看MV頁面中是怎麼實作這個function的
methods: {
filling ( offset , limit , first_load ) {
return new Promise ( resolve => {
mvs ( this . area , this . type , this . order , offset , limit )
. then ( result => {
if ( first_load ) this . total = result [ 'count' ]
resolve ( result [ 'data' ] )
} )
} )
}
}
這個function會被當做參數傳遞給
<rendering/>
而其內部又會傳遞給<pagination/>
並由它決定什麼時候調用mvs(area, type, order, offset, limit)
是一個後端mv資料的接口,前面三個參數用來決定返回什麼類型的mv,offset
,limit
則是分頁用的參數
當<pagination/>
渲染在頁面上的分頁元件被點擊時內部就會呼叫filling這個方法並傳遞一些參數過來,這些參數被mvs
接口用作分頁的參數, 在接口數據成功返回時通過resolve的方式傳遞給<pagination/>
內部,並將這次的資料快取起來,同時透過<Rendering/>
將資料傳遞給CommonVideoMatrices
這樣就能透過這些資料正常渲染了
頁面在第一次載入時filling也會被調用
可以看到我們這個頁面還需要在用戶選擇了其他標籤或分類後,重新加載新的數據, 你可能會想到通過監聽<simple-radio/>
的點擊事件然後通過某種方式通知<pagination/>
調用filling方法更新資料?
不用! ! 我們有更簡單的方式來實現這個功能
< rendering
...
:unique =" area + type + order "
/>
unique
最終會被傳遞給<pagination/>
area
type
order
它們都分別透過v-model
綁定在三個不同的<simple-radio/>
上
我只需要在<rendering/>
組件上添加一個unique
的prop並給它傳遞一個用於響應數據更新的值,當傳遞給unique
的值改變時filling就會被調用, 這會非常有用我們經常會遇到這種場景,例如當歌單的id被改變時, 重新加載新的歌單數據,這時我們只需要將id傳遞給unique
並實現一個filling方法,當id改變時就會自動加載新的歌單數據了。
可以看到在這個頁面中<Rendering/>
使用起來還是很方便的,我們在編寫這個頁面時可以只關注CommonVideoMatrices
的內容而不用去考慮數據的獲取方式跟邏輯,實際上在這個頁面中數據在加載時會顯示一個loading...的動畫效果,這些也是由<Rendering/>
來完成的,只不過在這裡展示的程式碼片段中這部分被精簡掉了
其實還有一個叫
adapter
的東西是用來解決後端在不同的地方回傳相同類型的資料但資料結構卻不太一樣的問題的,但我這裡就不介紹了
這是一個面向新手的項目,希望能給一些剛接觸前端/Vue又找不到什麼項目實踐的同學一些靈感和參考項目中的很多地方都是這樣實現的,相信你在看完這部分內容後能對本項目的部分源碼有比較清晰的了解