內容均爲《Vue2.0開發企業級移動端音樂Web App 》學習筆記javascript
包括 jsonp 原理介紹和 Promise 封裝、輪播圖組件開發、歌單接口數據分析和抓取、axios 介紹和後端接口代理、歌單列表組件開發和數據應用、scroll 組件的抽象和應用、vue-lazyloader 懶加載插件的介紹和應用、loading 基礎組件開發和應用。css
jsonp原理介紹html
動態生成一個JavaScript標籤,其src由接口url、請求參數、callback函數名拼接而成;利用js標籤沒有跨域限制的特性實現跨域請求前端
api/config.js 配置與接口統一的參數 爲了和QQ音樂接口一致,配置一些公用的參數、options和err_num碼vue
//爲了和QQ音樂接口一致,配置一些公用的參數、options和err_num碼
export const options={
param: 'jsonpCallback'
}
export const commonParams={
g_tk: 5381,
uin: 0,
inCharset:' utf-8',
outCharset:' utf-8',
notice: 0,
platform:'h5',
needNewCode: 1,
format: 'jsonp'
}
export const ERR_OK =0
複製代碼
common/js/jsonp.js Promise封裝java
//引入jsonp https://github.com/webmodules/jsonp
import originjsonp from 'jsonp'
//返回一個函數。傳入url。data 和callback(放在option裏面的callback)
export default function jsonp(url,data,option){
//對url和data進行拼接,拼接成一個新的url
//判斷原來的url中是否包含有?號 ,有問號加上 & 沒有 則加上? 而且拼接上data參數部分
url+=(url.indexOf('?')>-1 ? '&' : '?')+param(data);
//返回一個promise的實例。能夠直接用這個實例的then方法來獲取到resolve和reject狀態的回調
return new promise(function(resolve,reject){
//第一個參數是url,由於jsop是get請求,在此以前腰將基本的url和data進行拼接組成新的url
//第二個參數,option是配置好的callback的參數
//第三個參數,是回調函數
originjsonp(url, option,(err, data)=>{
if(!err){
resolve(data);
}else{
reject(data);
}
})
})
//對傳進來的data作拼接處理
function param(data){
let str='';
// data.keys().forEach(key => {
// str+=`${key}=${data[key]}&`
// });
// str.substring(0,str.length-1)//從第一位開始截取到最後一位
for(var key in data){
let value=data[key] !== undefined ? data[key] : ''
str+=`&${key}=${encodeURIComponent(value)}`
}
return str ? str.substring(1) :''
}
複製代碼
在api/recommend.js中封裝獲取推薦頁的數據node
//在這份js裏面寫請求recommend的異步請求方法
//引入須要的config配置的參數
import {commonParams,options} from './config'
import jsonp from 'common/js/jsonp.js'
//獲取到推薦的信息誰
export function getRecommend(){
const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg'
const data=Object.assign({},commonParams,{
platfrom: 'h5',
notice: 0,
uin: 0,
needNewCode: 1
})
return jsonp(url,data,options)
}
在recommend.vue中調用並獲取數據
//引入異步請求函數
import {getRecommend} from 'api/recommend.js'
//引入經常使用的配置參數的ERR_OK
import {ERR_OK} from 'api/config.js'
export default {
created(){
//用——開頭的方法名
this._getRecommend();
},
methods: {
//通常在methods裏面寫方法的具體實現,不要在created或者mounted等生命週期中寫,方便管理
_getRecommend(){
getRecommend().then(res=>{
if(res.code==ERR_OK){
console.log(res.data.slider);
}
})
}
},
}
複製代碼
開發一個slide組件,具體的實如今slide組件上實現,父組件只須要傳遞數據就能夠 輪播庫ios
cnpm install better-scroll@next -S
複製代碼
在slider組件上用slot來接收父組件傳遞過來的數據 在props定義可能會發生改變的配置,好比是否循環,是否自動播放,切換的時長 咱們在slider組件下位每一個傳遞進來的元素 加上類 addClass(item,'slide-item') 來控制樣式 addClass 咱們定義在common/js/dom.js中 common/js/dom.jsgit
//判斷元素中是否有className
export function hasClass(el,className){
let reg=new RegExp('(^|\\s)' + className + '(\\s|$)');
return reg.test(el.className);
}
//若是沒有類添加類
export function addClass(el,className){
if(hasClass(el,className)){
return
}
//將el的className拆開
let newClass= el.className.split(' ');
//去除空格以後將新的類參加的newClass的數組中
newClass.push(className);
//給el加上類
el.className=newClass.join(' ');
}
複製代碼
src\components\base\slider\slider.vue 實現過程github
計算slider的寬度和slider-content 和slider-content裏面的列表的寬度 props接收可能要修改的參數,loop爲循環列表,當loop爲true的時候,會在輪播的列表中copy先後兩個li作無縫處理,因此咱們在計算slider-content的寬度時,要判斷當前是不是無縫滾動
當窗口resize時,要從新計算寬度,同時refresh一下,refresh 從新計算 BetterScroll,當 DOM 結構發生變化的時候務必要調用確保滾動的效果正常。
當用手指滑動送開時,將會派發一個scrollEnd事件。在這裏咱們計算當前的currentPageIndex.,讓焦點正確active
let pageIndex = this.slider.getCurrentPage().pageX
this.currentPageIndex = pageIndex
複製代碼
this.slider.next()
複製代碼
this.slider.goToPage(index,0)
複製代碼
總體代碼
<template>
<div class="slider" ref="slider">
<div class="slider-content" ref="slidergroup">
<slot></slot>
</div>
<div class="dots">
<span class="dot" :class="{active:index===currentPageIndex}" v-for="(item,index) in dots" :key="index" @click="_gotopage(index);"></span>
</div>
</div>
</template>
<script> import {addClass} from 'common/js/dom' import BTscroll from 'better-scroll' export default { props:{ autoplay:{//是否自動播放 type:Boolean, default:true }, loop:{//是否無縫滾動 type:Boolean, default:true }, intervalTime:{ type:Number, default:4000 } }, data(){ return{ dots:0, currentPageIndex:0 } }, mounted() { this._getSliderWidth(); this._initSlider(); this._windowresize(); }, methods: { _initSlider(){ let me=this; this.slider=new BTscroll(this.$refs.slider,{ scrollX: true, scrollY: false, slide: { loop: me.loop, threshold: 100//可滾動到下一個或上一個的閾值。 }, momentum: false, bounce: false, stopPropagation: true, click:true }) //當滾動結束的時候,派發一個scrollEnd事件,咱們獲取到當前的index給currentpageindex this.slider.on('scrollEnd', ()=>{ this._scrollEnd() }) this._autoGoNext() }, _getSliderWidth(isresize){ let sliderWidth=this.$refs.slider.clientWidth; let children=[...this.$refs.slidergroup.children]; if(!isresize){ this.dots=children.length; } let width=0; children.forEach((item,index)=>{ width+=sliderWidth; item.style.width=sliderWidth+'px' //給每一個children加上類 addClass(item,'slider-item') }) if(!isresize && this.loop){ //當是無縫滾動的時候加上兩個 width+=2*sliderWidth } this.$refs.slidergroup.style.width=width+'px' }, //重刷新 refresh(){ this.slider && this.slider.refresh() }, //滑動結束的時候 _scrollEnd(){ //當滾動結束的時候,派發一個scrollEnd事件,咱們獲取到當前的index給currentpageindex let pageIndex = this.slider.getCurrentPage().pageX this.currentPageIndex = pageIndex this._autoGoNext() }, //自動播放 _autoGoNext(){ if(!this.autoplay){ return } clearTimeout(this.playTimer) this.playTimer = setTimeout(() => { this.slider.next() }, this.intervalTime) }, //去到下一頁 _gotopage(index){ this.slider.goToPage(index,0) this._autoGoNext() }, //當窗口發生變化的時候 _windowresize(){ window.addEventListener('resize',()=>{ this._getSliderWidth(true); this.refresh() }) } } } </script>
<style scoped> .slider{ position: relative} .slider-content:after{content:""; display:block; clear: both;} .slider-item{float:left} .slider-item a{width: 100%;} .slider-item img{ width: 100%;} .dots{position: absolute; right: 0; left: 0; bottom: 12px; -webkit-transform: translateZ(1px); transform: translateZ(1px); text-align: center; font-size: 0;} .dots .dot{ display: inline-block; margin: 0 4px; width: 8px; height: 8px; background: hsla(0,0%,100%,.5); border-radius:50%; } .dots .dot.active{ width: 20px;background: #fff; border-radius: 20px;} </style>
複製代碼
在recommend.vue中使用slider組件
<!--輪播部分-->
<slider v-if="recommend.length" >
<div v-for="(item,index) in recommend" :key="index" class="slide-item">
<a :href="item.linkUrl" target="_blank">
<img :src="item.picUrl">
</a>
</div>
</slider>
複製代碼
優化 App.vue 中優化:緩存DOM到內存中,不用從新發送請求,這樣slider就不會有閃動的現象
<keep-alive>
<router-view></router-view>
</keep-alive>
複製代碼
slider中優化:當組件中有定時器,必定要記得在組件銷燬時清理掉這些定時器,使用生命週期destroyed()
beforeDestroy() {
clearTimeout(this.playTimer)//消除定時器
this.slide.destroy()
},
複製代碼
問題: QQ音樂歌單數據的請求頭中有域名Host、來源Referer,因此請求的接口應該是有加上該域名和來源,直接請求就會報HTTP-500錯誤。 緣由: 前端不能直接修改request header,因此要經過後端代理的方式解決。 解決: 採用 axios 在node.js中發送http請求
在config/index.js中配置反向代理
//配置返向代理
 proxyTable: {
'/api/getRecomonList': {// '/api':匹配項
target: 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg',// 接口域名
     secure: false,// 若是是https接口,須要配置這個參數
changeOrigin: true,// 是否跨域
//請求前處理
bypass: function(req, res, proxyOptions) {
//對請求頭處理
req.headers.referer='https://y.qq.com'
req.headers.host='c.y.qq.com'
},
     pathRewrite: {// 重寫地址
      '^/api/getRecomonList': ''
}
}
},
複製代碼
axios的使用
//引入axios
import axios from 'axios'
export function getRecommendList(){
return new Promise(function(resolve,reject){
let url='/api/getRecomonList'
let data=Object.assign({}, commonParams, {
platform: 'yqq',
hostUin: 0,
sin: 0,
ein: 29,
sortId: 5,
needNewCode: 0,
categoryId: 10000000,
rnd: Math.random(),
format: 'json'
})
axios.get(url,{params:data}).then(res=>{
resolve(res.data)
}).catch(err=>{
reject(err);
})
})
}
複製代碼
注意這裏的data部分 {params:data}
recommend.vue中:定義和調用獲取數據的方法
created(){
this._getRecommendList()
},
_getRecommendList(){
let that=this;
getRecommendList().then(res=>{
if(res.code==ERR_OK){
that.recommendList=res.data.list
}
})
},
複製代碼
better-scroll滾動佈局:只會滾動父元素下的第一個子元素 —— 想要slider和recommend-list同時能夠滾動,須要在外層再嵌套一個
<div class="recommend">
<scroll class="recommend-content" :data="recommendList" ref="scroll">
<div>
//輪播部分
//...
//列表部分
</div>
</scroll>
</div>
複製代碼
recommend-content的高度要固定,因此咱們須要加上樣式
.recommend
position: fixed
width: 100%
top: 88px
bottom: 0
.recommend-content
height: 100%
複製代碼
在scroll.vue中的佈局
<template>
<div ref="wrapper">
<slot></slot>
</div>
</template>
複製代碼
scroll 組件
<template>
<div ref="wrapper">
<slot></slot>
</div>
</template>
<script> import BScroll from 'better-scroll' export default { props: { //probeType: 1 滾動的時候會派發scroll事件,會截流。2 滾動的時候實時派發scroll事件,不會截流 。3 除了實時派發scroll事件,在swipe的狀況下仍然能實時派發scroll事件 probeType: { type: Number, default: 1 }, // click: true 是否派發click事件,一般判斷瀏覽器派發的click仍是betterscroll派發的click,能夠用event._constructed,如果bs派發的則爲true click: { type: Boolean, default: true }, data: { type: Array, default: null } }, mounted(){ setTimeout(() => { //確保DOM已經渲染 this. _initScroll() }, 20) }, methods:{ _initScroll(){ if(!this.$refs.wrapper){ return } this.scroll = new BScroll(this.$refs.wrapper, { probeType : this.probeType, click: this.click }) }, refresh() { // 強制 scroll 從新計算,當 better-scroll 中的元素髮生變化的時候調用此方法 this.scroll && this.scroll.refresh() } }, watch:{ data() { setTimeout(() => { this.refresh() }, 20) } } } </script>
複製代碼
須要注意的是,由於頁面的數據是動態變化的,咱們沒法判斷是輪播圖的數據先響應完,仍是推薦列表的數據先響完, 因此當咱們在輪播圖的圖片第一次將容器撐開的時候,咱們就去調用一次scroll實例的refresh方法 給images加上onload事件
<img :src="item.picUrl" @load="loadImage">
複製代碼
loadImage(){
//由於有多張圖片,可是咱們只須要當只有一張圖片撐開的時候才作一下操做
if(!this.checkloaded){ //添加一個標誌位,若是load一次了,就再也不執行onload事件了
this.checkloaded = true
this.$refs.scroll.refresh()
}
}
複製代碼
在scroll組件傳入recommendList,,它和scroll組件接收的data相對應,咱們在scroll組件裏面監聽了data的變化,一旦變化了就調用refresh的方法,
作好以上兩點,纔可讓滾動列表正常滾動哦~~
推薦列表但是有好多好多圖片的哦,爲了有一個好的用戶體檢,處理圖片懶加載問題,vue-lazyload 。讓咱們用起來吧。
cnpm install vue-lazyload --save
複製代碼
在main.js中
import vulazyload from 'vue-lazyload'
Vue.use(vulazyload,{
loading: require('./common/image/default.png') //loading時默認顯示的圖片
})
複製代碼
將src改爲v-lazy
<img v-lazy="item.imgurl" width="60" height="60">
複製代碼
這樣,只有用戶滾動過的地方,圖片纔會加載,沒有看的地方,就不會進行加載 問題:fastclick和better-scroll的click會有衝突. 解決:slider中的添加一個class="needsclick",這是fastclick中的一個屬性
<img class="needsclick" :src="item.picUrl" @load="loadImage">
複製代碼
在列表沒有加載完成的時候,咱們能夠在頁面上展現一個loading loading組件的開發
<template>
<div class="loading">
<img width="24" height="24" src="./loading.gif">
<p class="desc">{{title}}</p>
</div>
</template>
<script> export default { props:{ title:{ type:String, default:'加載中' } } } </script>
<style scoped> .loading img{ text-align: center; margin: 10px auto; display: block; } .desc{ font-size: 14px; text-align: center; } </style>
複製代碼
在頁面上使用loading
<!--loading -->
<loading v-if="!recommendList.length"></loading>
複製代碼