第4章 推薦頁面開發

內容均爲《Vue2.0開發企業級移動端音樂Web App 》學習筆記javascript

包括 jsonp 原理介紹和 Promise 封裝、輪播圖組件開發、歌單接口數據分析和抓取、axios 介紹和後端接口代理、歌單列表組件開發和數據應用、scroll 組件的抽象和應用、vue-lazyloader 懶加載插件的介紹和應用、loading 基礎組件開發和應用。css

4-1 頁面簡介+輪播圖數據分析

4-2 jsonp原理介紹+Promise封裝

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

  1. 引入jsonp庫
  2. 封裝一個jsonp的函數,傳三個參數,url,data,和callback
  3. 對傳遞進來的data和url進行拼接
  4. 用jsonp請求,返回promise實例
//引入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) :''
}

複製代碼

4-3 jsonp的應用+輪播圖數據抓取

在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);
                }
            })
       }
    },
}

複製代碼

4-4 輪播圖組件實現(上)

開發一個slide組件,具體的實如今slide組件上實現,父組件只須要傳遞數據就能夠 輪播庫ios

cnpm install better-scroll@next -S
複製代碼

4-5 輪播圖組件實現(中)

在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

  1. 計算slider的寬度和slider-content 和slider-content裏面的列表的寬度 props接收可能要修改的參數,loop爲循環列表,當loop爲true的時候,會在輪播的列表中copy先後兩個li作無縫處理,因此咱們在計算slider-content的寬度時,要判斷當前是不是無縫滾動

  2. 當窗口resize時,要從新計算寬度,同時refresh一下,refresh 從新計算 BetterScroll,當 DOM 結構發生變化的時候務必要調用確保滾動的效果正常。

  3. 當用手指滑動送開時,將會派發一個scrollEnd事件。在這裏咱們計算當前的currentPageIndex.,讓焦點正確active

let pageIndex = this.slider.getCurrentPage().pageX
 this.currentPageIndex = pageIndex
複製代碼
  1. 當自動播放的時候,咱們先清除定時器,再啓動。讓頁面自動播放到下一頁
this.slider.next()
複製代碼
  1. 當點擊焦點的時候,務必要設置滾動配置的click爲true,否則點擊不起效果
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>
複製代碼

4-6 輪播圖組件實現(下)

在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()
},

複製代碼

4-7 歌單數據接口分析

問題: QQ音樂歌單數據的請求頭中有域名Host、來源Referer,因此請求的接口應該是有加上該域名和來源,直接請求就會報HTTP-500錯誤。 緣由: 前端不能直接修改request header,因此要經過後端代理的方式解決。 解決: 採用 axios 在node.js中發送http請求

4-8 axios 的使用和後端接口代理

在config/index.js中配置反向代理

//配置返向代理
  &emsp;proxyTable: {
        '/api/getRecomonList': {// '/api':匹配項
          target: 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg',// 接口域名
  &emsp;&emsp;&emsp;&emsp; secure: false,// 若是是https接口,須要配置這個參數
          changeOrigin: true,// 是否跨域
          //請求前處理
          bypass: function(req, res, proxyOptions) {
            //對請求頭處理
            req.headers.referer='https://y.qq.com'
            req.headers.host='c.y.qq.com'
          },
  &emsp;&emsp;&emsp;&emsp; pathRewrite: {// 重寫地址
  &emsp;&emsp;&emsp;&emsp;&emsp;  '^/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}

4-9 歌單列表組件開發和數據的應用

recommend.vue中:定義和調用獲取數據的方法

created(){
       this._getRecommendList()
    },


 _getRecommendList(){
           let that=this;
           getRecommendList().then(res=>{
               if(res.code==ERR_OK){
                   that.recommendList=res.data.list
               }
           })
       },
複製代碼

4-10 scroll 組件的抽象和應用(上)

better-scroll滾動佈局:只會滾動父元素下的第一個子元素 —— 想要slider和recommend-list同時能夠滾動,須要在外層再嵌套一個

,將兩個元素包裹起來 在recommend中

<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>
複製代碼

4-11 scroll 組件的抽象和應用(下)

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的方法,

作好以上兩點,纔可讓滾動列表正常滾動哦~~

4-12 vue-lazyload 懶加載插件介紹和應用

推薦列表但是有好多好多圖片的哦,爲了有一個好的用戶體檢,處理圖片懶加載問題,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">
複製代碼

4-13 loading 基礎組件的開發和應用

在列表沒有加載完成的時候,咱們能夠在頁面上展現一個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>
複製代碼

github

相關文章
相關標籤/搜索