1. 程式人生 > >基於Vue2.x開發的音樂播放器app(推薦介面+懶載入+axios獲取後端介面實現)

基於Vue2.x開發的音樂播放器app(推薦介面+懶載入+axios獲取後端介面實現)

1、專案開發需求分析:

包含四個層面——

(1)推薦模組

(2)歌手模組

(3)排行模組

(4)搜尋模組

2、專案開發流程

(1)搭建專案:藉助vue-cli腳手架工具,具體請參考部落格: ...;

由於專案存放在本地電腦E盤VueTest目錄下,

cd E:\VueTest


圖1:切換專案存放目錄、vue-cli搭建初始專案

通過腳手架vue-cli工具搭建專案

vue init webpack vue-music

選擇配置專案相關webpack

(2)進入專案vue-music

cd vue-music

安裝專案node_mudules依賴(在這裡採用淘寶映象cnpm,可加快安裝效率)

cnpm install

上述流程具體操作見(圖2:安裝專案依賴)

    

圖2:安裝專案依賴

在本地載入該專案工程檔案

npm run dev


                     圖3:搭建成功介面

當看到localhost:8080/8081時,說明專案工程環境搭建成功。


圖4:本地搭建環境成功

當顯示上述介面,則表示vue-cli腳手架搭建的vue-music專案成功。

(3)在編輯器中開啟該專案工程檔案(在該環節中我採用的編輯器:VS Code)

專案目錄中所以開發主要基於src目錄作為主載


圖5:專案工程目錄

2.3-1——目錄src

api:主要存放後臺請求相關的程式碼,包括ajax,jsonp等,

有.gitkeep檔案表示雖為空,但可上傳至git上

common:主要存放通用的靜態資源,包括字型圖示fonts、圖片images、指令碼檔案js庫、樣式檔案stylus

   stylus——

base.styl:基礎樣式,引用variable.styl

variable.styl:以“變數定義”方式引入樣式檔案(顏色、字型定義規範)

icon.styl:字型圖示樣式檔案

index.styl:引入reset、base、icon三者樣式檔案

mixin.styl:定義一些函式,方便元件.vue檔案中引用

reset.styl:重置樣式檔案


圖6:common目錄下stylus 

components:主要存放專案中元件化.vue檔案

router:主要存放靜態路由相關程式碼index.js

store:主要存放vuex相關程式碼

App.vue:

main.js:主要存放渲染app檔案的js指令碼

2.3-2:更改相關檔案

在build資料夾下,webpack.base.conf.js中

resolve: { extensions: ['.js','.vue','.json'], alias: { 'src':resolve('src'), 'common':resolve('src/common') } },

這樣,在專案中可以這麼使用,如下:

import 'common/stylus/index.styl'

3、頁面骨架開發

(1)頁面入口+header

首先,package.json檔案做以下更改

"dependencies": { "babel-runtime": "^6.26.0", "vue": "^2.5.2", "vue-router": "^3.0.1", "fastclick": "^1.0.6" },

在“devDependencis”下,新增下面babel-polyfill、stylus、stylus-loader三個依賴,

執行cnpm install

"stylus":"^0.54.5", "babel-polyfill": "^6.2.0", "stylus-loader": "^3.0.1"

(2)路由配置+頂導元件開發

router靜態路由——根元件預設指向

vue-router(index.js)——new Vue({ router })(main.js)——<router-view/>(App.vue)

main.js——babel-polyfill、fastClick

在http://localhost:8080/#中可以用靜態路由vue-router實現介面間切換


圖7:vue-router實現介面跳轉

解析:由於在專案工程檔案中指定根路徑path:/recommend,所以預設為該介面,同時點選不同的tab選項列   表,可由靜態路由進入不同的元件。

import Routerfrom'vue-router' Vue.use(Router)
export defaultnewRouter({ routes: [ // 根元件預設的指向 { path: '/', component: Recommend }, { path: '/recommend', component: Recommend }, { path: '/singer', component: Singer }, { path: '/rank', component: Rank }, { path: '/search', component: Search } ] })

4、推薦模組開發

locahost:8080/#/recommend

疑難點:

a、資料獲取——線上真實資料,非模擬。用qq音樂進行抓取資料,(採用jsonp進行抓取線上資料)

                       

圖8:qq音樂播放器抓取資料

b、jsonp原理:可參考github地址(https://github.com/webmodules/jsonp


圖9:github-jsonp地址

c、在index.js可檢視具體實現原理

(1)Install

Install for node.js or browserify usingnpm:

$ npm install jsonp

Install for component(1) using component:

$ component install LearnBoost/jsonp

Install for browser using bower:

$ bower install jsonp

(2)API

jsonp(url, opts, fn)

  • url (String) url to fetch
  • opts (Object), optional
    • param (String) name of the query string parameter to specify the callback (defaults to callback)
    • timeout (Number) how long after a timeout error is emitted. 0 to disable (defaults to 60000)
    • prefix (String) prefix for the global callback functions that handle jsonp responses (defaults to __jp)
    • name (String) name of the global callback functions that handle jsonp responses (defaults to prefix + incremented counter)
  • fn callback
操作——在package.json下定義jsonp,再cnpm install安裝該依賴 "dependencies": { "babel-runtime": "^6.26.0", "vue": "^2.5.2", "vue-router": "^3.0.1", "fastclick": "^1.0.6", "jsonp": "^0.2.1" },

jsonp的封裝“./src/common/js/jsonp.js”

// jsonp封裝(用promise實現封裝) import originJSONPfrom'jsonp'
export defaultfunctionjsonp(url,data,option) { // url拼接 url += (url.indexOf('?') <0 ?'?' :'&') +param(data) // Promise()回撥函式,類似於callback,三種狀態:pending(進行中)/resolve(已完成)/reject(已失敗) return newPromise((resolve,reject)=> { originJSONP(url,option, (err,data)=> { if (!err) { resolve(data) } else { reject(err) } }) }) }
// 將data轉換為json格式 function param(data) { let url ='' for (varkindata) { let value =data[k] !==undefined ?data[k] :'' url += `&${k}=${encodeURIComponent(value)}` } return url ?url.substring(1) :'' }

jsonp使用:對其以promise方式封裝,同時在/api目錄下封裝獲取資料的方法

(1)輪播圖

4.1-1資料抓取

components——recommend.vue“slide-wrapper”

api——recommend.js、config.js

js——jsonp.js

當上述檔案編譯成功,在開發者工具F12下的console控制檯程式,會顯示如下:


圖10:qq音樂輪播圖資料抓取成功介面

4.1-2:輪播圖元件開發

目錄base基礎目錄——src/base/slider/slider.vue,

slot插槽<slot></slot>

<div class="slider"> <!-- slotA與slotE有什麼區別 --> <div class="slider-group"> <slot></slot> </div>
<div class="dots">
</div> </div>

輪播圖模組實現:

   實現方式很多,在這裡藉助better-scroll庫實現Slider元件開發

   屬性:snap——false,針對slider開發,普通列表滾動不需要配置

         snap——false 是否可以無縫迴圈輪播

 snapSpeed——400,輪播圖切換的動畫時間

4.1-2-1:slider-group(輪播圖橫向點選無縫滾動)

a、初始化Scroll

首先,在package.json中安裝依賴,並在命令列中執行“cnpm install”

"dependencies": { "better-scroll": "^1.4.2", }

其次,在slider.vue元件中引入該庫

import BScrollfrom"better-scroll"

b、計算setSliderWidth,在methods方法中,定義_setSliderWidth()

// 設定輪播寬度 _setSliderWidth() { // 獲得列表元素多少 this.children =this.$refs.sliderGroup.children
// 設定寬度/每個slider寬度均為元素的clientWidth let width =0 let sliderWidth =this.$refs.slider.clientWidth for(leti=0;i<this.children.length;i++) { // 先獲取每個子元素 let child =this.children[i] // import addClass方法,並傳入引數 addClass(child,'slider-item') // 設定每個child的寬度 child.style.width =sliderWidth +'px' width += sliderWidth }
// loop需要克隆2倍寬度 if(this.loop) { width += 2*sliderWidth } this.$refs.sliderGroup.style.width =width + 'px' },

c、初始化輪播圖slider,methods中_initSlider()方法,此時引用a中BScroll外掛

// 初始化輪播圖 _initSlider() { // 繫結DOM元素slider,options配置 this.slider =newBScroll(this.$refs.slider, { // 只允許橫向滾動,不允許縱向 scrollX: true, scrollY: false, momentum: false,// 慣性 snop: true,//無縫滾動 snopLoop: this.loop, snopThreshold: 0.3, snopSpeed: 400, click: true }) }

4.1-2-2:dots設定自動輪播

為了實現該功能,必須在初始化initSlider()之前初始化dos,

首先,在data()中定義一個dots物件

data() { return { dots: [] } },

其次,在methods方法中定義_initDots()

// 初始化dots _initDots() { this.dots =newArray(this.children.length) },
最後,在mounted鉤子裡呼叫該方法,在_initSlider()之前 // mounted鉤子 mounted() { setTimeout(() => { this._setSliderWidth() this._initDots() this._initSlider() }, 20); },

測試:當不清除定時器時,由於在自動播放autoPlay()之前,為手動播放,所以需要清除clearInteral()

// 給slider繫結scrollEnd()事件() => {}回撥函式 this.slider.on('scrollEnd', ()=> { // 表示第幾個子元素 let pageIndex =this.slider.getCurrentPage().pageX if(this.loop) { pageIndex -= 1 } this.currentPageIndex =pageIndex
// 測試:由於上述程式碼執行只滾動一次,所以該事件scrollEnd有bug,對其新增autoPlay進行測試 if(this.autoPlay) { // 由於在自動播放的時候,是手動播放,所以需要clear定時器 clearInterval(this.timer) this._play() } })

執行效果圖如下:(注意:可以自動播放,擷取的是靜態圖)


優化: 當發現在控制檯上切換不同的螢幕大小的時候,介面顯示出現錯誤,如下:


解析:上述bug出現的主要原因是因為_setSliderWidth()中sliderWidth出現了小問題,即window下的resize

解決辦法:監聽一個window下resize事件

// 優化sliderWidth寬度不一的問題 window.addEventListener('resize', ()=> { // 當slider未初始化 if(!this.slider) { return } // 判斷是否有isResize(), this._setSliderWidth(true) // 寬度發生變化,需要重新計算重新整理以下slider this.slider.refresh() }) 此外,在_setSliderWidth()事件,應該傳入引數isResize做判斷,同時執行函式時,初始下不需要isResize(),但到重新計算寬度時,就不能width*2 // 若loop自動播放為true,且不需要resize時,loop需要克隆2倍寬度 if(this.loop && !isResize) { width += 2*sliderWidth }

重新編譯後,會發現無論是在移動端進行測試,還是切換螢幕大小,在pc或Mac端測試,寬度均不會出現問題,

但是,當我們重新切換或者載入的時候,recommend.vue生命週期,以及相關資料均會重新載入一遍才會重新整理到資料,導致體驗差,

優化1:App.vue中,將router-view加一個keep-alive進行dom快取

<keep-alive> <router-vxew></router-view> </keep-alive>

優化2:slider.vue中,export default({})加一個destroyed()清除快取定時器

// destroyed生命週期,當存在暫存器,元素銷燬時機,清除資源 destroyed() { clearTimeout(this.timer) }

4.1-3:歌單列表元件開發

a、歌單資料介面分析

在這裡抓取的是PC端歌單資料,地址:https://y.qq.com/portal/playlist.html

               

從上述console控制檯下NetWork  general下的requestUrl即為表單資料介面地址

https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg

b、實現原理

首先,定義介面獲取函式。包括url以及data相關資料,在recommend.js

// 獲取歌單介面資料 export functiongetDiscList() { // 歌單介面url地址 const url ='https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg'
// 定義表單介面資料 const data =Object.assign({}.commonParams, { platform: 'yqq', hostUin: 0, needNewCode: 0, categoryId: 10000000, sortId: 5, sin: 0, ein: 29, rnd: Math.random() })
return jsonp(url,data,options) }

其次,在recommend.vue元件下呼叫該獲取介面資料函式

created() { // 獲取輪播圖圖片介面資料函式 this._getRecommend(), // 獲取歌單介面資料函式 this._getDiscList() },

同時,在表頭引入該函式

import {getRecommend,getDiscList}from'api/recommend'

c、後端介面代理axios

  由於若使用jsonp進行資料抓取,會導致一些錯誤,因為qq音樂伺服器端不會識別,只能在專案build目錄下dev-server.js下,配置與qq音樂相同的headers——referer、host相同,這樣伺服器端識別完成,相當於後端代理的方法獲取了後臺的歌單介面資料。

  採用ajax http請求API axios庫https://github.com/mzabriskie/axios

首先,package.json上寫入axios,並在cmd命令列cnpm install安裝依賴

"dependencies": { "axios": "^0.17.1" },

在build/dev-server.js中中,設定Axios後端介面代理,用於獲取介面資料,再進行調取相應的api。

const axios =require('axios') const app =express()
// define apiRoutes const apiRoutes =express.Router()
// 後端介面代理 apiRoutes.get('/getDiscList',function(req,res) { // ajax庫axios const url ='https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' axios.get(url, { headers: { referer:'https://c.y.qq.com/', host:'c.y.qq.com' }, params:req.query }).then((response)=> { res.json(response.data) }).catch((e)=> { console.log(e) }) })
app.use('/api',apiRoutes)

未完待續...(由於axios請求介面一直處於404失敗狀態,所以recommend元件結合dev.server.js配置未達到預期的效果,還需要做到後期的改善)

4.1-4:scroll滾動元件,scroll.vue

  由於scroll滾動適用於所有模組介面的滑動,所以作為一個元件進行開發,結合slot插槽進行使用

<template> <div ref="wrapper"> <slot></slot> </div> </template>

  在<script/>檔案主要用於設定檔案所需的指令碼渲染,包括import、export模組的引入,再加上屬性之間的渲染,

methods: { // 定義一個初始化scroll的方法,在mounted()中使用 _initScroll() { if(!this.$refs.wrapper) { return } this.scroll =new BScroll(this.$refs.wrapper, { probeType: this.probeType, click: this.click }) }, // 若存在即呼叫enable() enable() { this.scroll &&this.scroll.enable() }, // 否則呼叫disable() disable() { this.scroll &&this.scroll.disable() }, // 重新整理scroll重新定義高度 refresh() { this.scroll &&thid.scroll.refresh() } },

初始化完_initScroll()方法後,需要在mounted()函式中使用,

// 鉤子函式 mounted() { setTimeout(() => { this._initScroll() }, 20) },

其次,採用watch進行監聽資料data變化

// 監聽器 watch: { data() { setTimeout(() => { this.refresh() }, 20) } }

4-3:圖片懶載入

與圖片“延遲載入”的js、jq實現原理一致:

1 設定一個定時器,計算每張圖片是否會隨著滾動條的滾動,而出現在視口(也就是瀏覽器中的 展現網站的空白部分 )中;

2 為<img>標籤設定一個暫存圖片URL的自定義屬性(例如loadpic),當圖片出現在視口時,再將loadpic的值賦給圖片的src屬性;

優點:(1)節省流量

      (2)提升載入速度

引用第三方外掛:vue-lazyload外掛

  https://github.com/523451928/vue-lazyload

使用方法

  • 1、直接頁面引用vue-lazyload.js(注意我自己使用的vue-cli,所以在js檔案裡面最後匯出了L