第4章 推荐页面开发

第4章 推荐页面开发

在这里插入图片描述
包括 jsonp 原理介绍和 Promise 封装、轮播图组件开发、歌单接口数据分析和抓取、axios 介绍和后端接口代理、歌单列表组件开发和数据应用、scroll 组件的抽象和应用、vue-lazyloader 懒加载插件的介绍和应用、loading 基础组件开发和应用。

4-1 页面简介+轮播图数据分析

4-2 jsonp原理介绍+Promise封装

jsonp原理介绍

动态生成一个JavaScript标签,其src由接口url、请求参数、callback函数名拼接而成;利用js标签没有跨域限制的特性实现跨域请求

api/config.js
配置与接口统一的参数
为了和QQ音乐接口一致,配置一些公用的参数、options和err_num码

//为了和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封装

  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中封装获取推荐页的数据

//在这份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组件上实现,父组件只需要传递数据就可以
轮播库

 cnpm install better-scroll@next -S

4-5 轮播图组件实现(中)

在slider组件上用slot来接收父组件传递过来的数据
在props定义可能会发生改变的配置,比如是否循环,是否自动播放,切换的时长
我们在slider组件下位每个传递进来的元素 加上类 addClass(item,‘slide-item’) 来控制样式
addClass 我们定义在common/js/dom.js中
common/js/dom.js

//判断元素中是否有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
实现过程

  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中配置反向代理

 //配置返向代理
   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}

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 chapter4

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值