vue 移动端音乐(2) 获取推荐数据并在相应的组件中接收

推荐界面(轮播图+歌单列表)+歌单详情页,重点是数据的获取,在线抓取

获取数据Jsonp2,解决跨域问题,在任何站点都可以通过Jsonp去请求这个接口来获取数据

原理:不是ajax请求,是动态创建script标签进行跨域的,将script的src指向我们请求的服务端地址,npm安装jsonp,axios,编写jsonp.js引入jsonp,并对原来的jsonp做一个简单的封装

1. src->common->js中,编写jsonp.js,引入jsonp插件,并对其做一个简单的封装

import originJsonp from 'jsonp'

// url是一个地址,请求通过data拼接到url上,并返回一个promise
export default function jsonp(url, data, option) {
  // url没有?时要先添加一个
  url += (url.indexOf('?') < 0 ? '?' : '&') + param(data)

  return new Promise((resolve, reject) => {
    originJsonp(url, option, (err, data) => {
      if (!err) {
        resolve(data)
      } else {
        reject(err)
      }
    })
  })
}

// 对要与url拼接的data进行处理
export function param(data) {
  let url = ''
  for (var k in data) {
    let value = data[k] !== undefined ? data[k] : ''
    url += '&' + k + '=' + encodeURIComponent(value)
  }
  return url ? url.substring(1) : ''
}

用promise对原生的jsonp进行封装之后,即可用jsonp真实的抓取我们的线上数据,获取数据通常会获取一些方法,在src-api目录下给每一个部分封装获取它相关数据的方法,我们这里做的是推荐相关,所以在这里建一个recommend.js,实现getRecommend方法,这个方法的原理就是利用jsonp获取数据,所以要先import jsonp,首先要定义url,data指的就是Query String Parameters,我们将相同的参数进行封装,所以在configure.js中进行公共参数的封装

2. 在api->config.js中统一参数格式

 

export const commonParams = {
    g_tk: 1251607169,
    inCharset: 'utf-8',
    outCharset: 'utf-8',
    notice: 0,
    format: 'jsonp'
}

export const options = {
    param: 'jsonpCallback'
}

// "code":0,正确的值为0
export const ERR_OK = 0

3. 引入jsonp.js和conifg.js,编写recommend.js获取jsonp数据

import jsonp from 'common/js/jsonp'
import {commonParams, options} from './config.js'
import axios from 'axios'
export function getRecommend() { // 利用jsonp抓取推荐数据
    const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg'
    const data = Object.assign({}, commonParams, { // assign合并对象
        platform: 'h5',
        uni: 0,
        needNewCode: 1
    })

    return jsonp(url, data, options)
}

export function getDiscList() {
    const url = '/api/getDiscList'
    const data = Object.assign({}, commonParams, {
      platform: 'yqq',
      hostUin: 0,
      sin: 0,
      ein: 29,
      sortId: 5,
      needNewCode: 0,
      categoryId: 10000000,
      rnd: Math.random(),
      format: 'json' // 将format从jsonp修改为json
    })
    return axios.get(url, {
      params: data
    }).then((res) => {
      return Promise.resolve(res.data)
    })
}

3.在recommend.vue中接收并处理recommend.js获取的数据,getDiscList是后来获取的歌单列表

import {getRecommend, getDiscList} from 'api/recommend' // 引入js中定义的方法
import {ERR_OK} from 'api/config'

在data中自定义变量结束recommend和discList,discList的获取在后边介绍

data() {
    return {
      recommends: [],
      discList: []
    }
  }

在created()调用方法,获取数据

created() {
  //  setTimeout(() => {
  //    this._getRecommend() 
  //  }, 2000);
    this._getRecommend() 
    // 模拟loading效果
   // setTimeout(() => {
    //  this._getDiscList()
   // }, 1000);
      this._getDiscList()
  },
  methods: {
    _getRecommend() {
      getRecommend().then((res) => { // res对应的就是json对象,之前已经import该方法了
        if (res.code === ERR_OK) {
          // console.log(res.data.slider)
          this.recommends = res.data.slider
        }
      })
    },
    _getDiscList() {
      getDiscList().then((res) => {
          if (res.code === ERR_OK) {
            // console.log(res.data.list)
            this.discList = res.data.list
          }
      })
    }
}

4接下来将获取的数据应用到轮播图组件上.

写一个轮播图组件src-base-slider.vue,将数据填充到DOM中,在src目录下新建base文件夹,用来存放基础组件,base->slider->slider.vue

1)在recommend.vue中注册并引入slider,将3中recommends数据中的图片和图片的超链接传给slider.vue

首先,created中获得的slider数据在data中保存:

data() {
    return {
      recommends: [],
      discList: []
    }
  }

​

编写DOM:

        <div v-if="recommends.length" class="silder-wrapper">
          <slider>
            <div class="needsclick" @load="loadImage" v-for="item in recommends" :key="item.id">
              <a :href="item.linkUrl">
                <img :src="item.picUrl" >
              </a>
            </div>
          </slider>
        </div>

2)编写slider组件,slider可以实现手动滑动,自动轮播,首先实现手动播放,然后是自动轮播,slot的作用:外部引入slider

的时候,slider包裹的DOM会被插入到插槽的部分;现在props中定义一些外部组件控制silder组件的一些变量

        props: {
            loop: { // 循环轮播,手动滑动
                type: Boolean,
                default: true
            },
            autoPlay: { // 自动轮播
                type: Boolean,
                default: true
            },
            interval: { // 轮播间隔
                type: Number,
                default: 4000
            }
        }

slider可以使用betterScroll来实现,主要是用来监听一些touch事件。我们在 mounted钩子函数中对betterScroll进行初始化,因为是横向滚动,所以在初始化之前我们要设置slider的宽度:编写this._setSliderWidth()和_initSlider()方法,并在mounted中调用他们,首先为DOM元素添加引用

  <div class="slider" ref="slider"> <!-- 外层容器 -->
    <div class="slider-group" ref="sliderGroup"> <!-- 内部元素,要设置sliderGroup的宽度,因为横向不能被自动撑宽-->

计算宽度:

            // 横向滚动初始化bscroll之前要下计算宽度,因为横向不能被自动撑宽
            _setSliderWidth(isResize) {
                this.children = this.$refs.sliderGroup.children
               
                let width = 0
                // 父容器宽度,设置每张图片的宽度都与父容器的宽度相同
                let sliderWidth = this.$refs.slider.clientWidth 
                for (let i = 0; i < this.children.length; i++) {
                    // 获取到每一张图片
                    let child = this.children[i]
                    // 为child添加样式,因为不可能在父组件加载样式的时候手动添加样式
                    addClass(child, 'slider-item')
                    // 为每张图片设置宽度
                    child.style.width = sliderWidth + 'px'
                    // 计算所有图片的总宽度
                    width += sliderWidth
                }
                // loop轮播实现的时候会左右克隆两个DOM,所以长度要增加
                if (this.loop && !isResize) {
                    width += 2 * sliderWidth
                }
                // 设置slider-group(内容区的宽度)
                this.$refs.sliderGroup.style.width = width + 'px'
            }

其中,methods中,计算宽度,其中将添加样式的方法addClass有dom.js引入,src-common-js-dom.js

import {addClass} from 'common/js/dom'
export function hasClass(el, className) { // 如果DOM已经有class了就不用再添加了
  let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
  return reg.test(el.className) // 满足class的定义方式就返回true
}
// DOM对象,DOM对象的className(样式的class)
export function addClass(el, className) { 
  if (hasClass(el, className)) {
    return
  }
  // 添加class,添加之前可能已经有别的class了,新旧class拼接
  let newClass = el.className.split(' ')
  newClass.push(className)
  el.className = newClass.join(' ')
}

我们在mounted中初始化高度,因为在recommend组件中,created获取数据是异步执行的,所以在slider组件中mounted执行的时候,slot中可能没有数据。我们要确保mounted执行的时候slot中是有值的,我们可以在recommend中执行slider组件之前添加v-if进行条件约束,判断推荐数据有值了之后在进行组件的引入;

        <div v-if="recommends.length" class="silder-wrapper">
          <slider>

 

mounted() {
            // 保证DOM成功渲染的话可以加一个延时,也可以用nextTick()
            setTimeout(() => {
                // 将初始化代码封装到methods中
                this._setSliderWidth()
                this._initDots() 
                this._initSlider()

                if (this.autoPlay) { // 是否要自动播放
                    this._play()
                }
            }, 20) // 浏览器刷新通常是17毫秒一次

            window.addEventListener('resize', () => {
                if (!this.slider) { // bScroll没有初始化的时候
                    return
                }
                // 窗口大小改变时重新计算宽度
                this._setSliderWidth(true)
                this.slider.refresh()
            })
        },

之后,编写BS初始化函数,初始化函数也写在mountend中

        _initSlider() {
                this.slider = new BScroll(this.$refs.slider, {
                    scrollX: true,
                    scrollY: false,
                    momentum: false, // 惯性
                    snap: true, 
                    snapLoop: this.loop,
                    snapThreshold: 0.3,
                    snapSpeed: 400
                   // click: true // 超链接不能点击的问题,fastClick的问题
                })
            }

这样我们就实现了鼠标拖动的轮播滚动,接下来我们要去实现dots和自动的轮播滚动;首先在slider组件中添加dots,因为在无缝滚动的时候,我们复制了两幅图片,所以我们要在init_slider之前初始化dots,保证dots的个数与原图片个数相同;

                // 将初始化代码封装到methods中
                this._setSliderWidth()
                this._initDots() 
                this._initSlider()

同时,在data中定义这个dots,dots默认是空数组

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

——initDots函数,只需要初始化一个数组即可

        _initDots() {
                this.dots = new Array(this.children.length)
            }

之后,便可以在DOM中渲染dots了,滚动到当前点是,dot会放大

<template>
  <div class="slider" ref="slider"> <!-- 外层容器 -->
    <div class="slider-group" ref="sliderGroup"> <!-- 内部元素,要设置sliderGroup的宽度-->
      <slot>
      </slot>
    </div>
    <div class="dots">
        <span class="dot" v-for="(item,index) in dots" :key="item, index" :class="{active: currentPageIndex === index}"></span>
    </div>
  </div>
</template>

之后,我们去维护currenPageIndex的值,在data中定义,并初始化为0;在bs滚动的时候是会派发一个事件的,所以我们在初始化slider的时候维护index的值

         _initSlider() {
                this.slider = new BScroll(this.$refs.slider, {
                    scrollX: true,
                    scrollY: false,
                    momentum: false, // 惯性
                    snap: true, 
                    snapLoop: this.loop,
                    snapThreshold: 0.3,
                    snapSpeed: 400
                   // click: true // 超链接不能点击的问题,fastClick的问题
                })

// 每一次滚动完一张图片时更新currentPageIndex的值,若是在自动轮播模式下,要添加play方法
                this.slider.on('scrollEnd', () => {
                    let pageIndex = this.slider.getCurrentPage().pageX 
                    if (this.loop) { // 循环模式下添加拷贝,所以index要减一
                        pageIndex -= 1
                    }
                    this.currentPageIndex = pageIndex

接下来还要添加一个自动播放的功能,就是props中的autoplay属性,在moutend中的setTime中,我们要判断是否为自动播放,如果是自动播放的话,我们要添加一个播放函数_play();

     setTimeout(() => {
                // 将初始化代码封装到methods中
                this._setSliderWidth()
                this._initDots() 
                this._initSlider()

                if (this.autoPlay) { // 是否要自动播放
                    this._play()
                }
            }, 20) // 浏览器刷新通常是17毫秒一次

play方法的核心就是修改pageIndex,让其跳转到下一张图片

        _play() { // 自动轮播
                let pageIndex = this.currentPageIndex + 1 // 播放下一张
                if (this.loop) { // 如果是一个循环加1,因为this.currentPageIndex是从0开始的
                    pageIndex += 1 // 所以pageIndex所对应的元素要比他多一个
                    // 设置定时器自动播放
                    this.timer = setTimeout(() => {
                        // goToPage是BScroll的方法,下标,方向,0代表x方向,间隔400
                        this.slider.goToPage(pageIndex, 0, 400)
                    }, this.interval) // 在props中定义的轮播的间隔
                }
            }

上述自动播放中,我们用的是setTimeout,只会重播一次,所以在_initSlider中,判断是否是自播放,若是的话调用自动播放函数this._play()

 // 自动轮播一次就会停止问题,添加自动播放函数
                    if (this.autoPlay) {
                        clearTimeout(this.timer)
                        this._play()
                    }


视口宽度改变时。轮播图错乱,解决办法,在mounted中监听视口大小的变化,这是因为视口的宽度改变了,但是我们之前为图片设置的宽度还是视口宽度变化前的宽度,所以在mounted中我们只需要监听window.resize事件

        mounted() {
            // 保证DOM成功渲染的话可以加一个延时,也可以用nextTick()
            setTimeout(() => {
                // 将初始化代码封装到methods中
                this._setSliderWidth()
                this._initDots() 
                this._initSlider()

                if (this.autoPlay) { // 是否要自动播放
                    this._play()
                }
            }, 20) // 浏览器刷新通常是17毫秒一次

            window.addEventListener('resize', () => {
                if (!this.slider) { // bScroll没有初始化的时候
                    return
                }
                // 窗口大小改变时重新计算宽度
                this._setSliderWidth(true)
                this.slider.refresh()
            })
        }

但是我们不能每次在初始化的时候都将图片个数加2,所以我们设置一个标志位,isResize,表示这个函数是不是resize过来的,如果是resize过来的,我们计算总宽度的时候就不加那两张图片了

            // 横向滚动初始化bscroll之前要下计算宽度,因为横向不能被自动撑宽
            _setSliderWidth(isResize) {
                this.children = this.$refs.sliderGroup.children
               
                let width = 0
                let sliderWidth = this.$refs.slider.clientWidth
                for (let i = 0; i < this.children.length; i++) {
                    // 获取到每一张图片
                    let child = this.children[i]
                    // 为child添加样式
                    addClass(child, 'slider-item')
                    // 为每张图片设置宽度
                    child.style.width = sliderWidth + 'px'
                    // 计算所有图片的总宽度
                    width += sliderWidth
                }
                // loop轮播实现的时候会左右克隆两个DOM,所以长度要增加
                if (this.loop && !isResize) {
                    width += 2 * sliderWidth
                }
                // 设置slider-group(内容区的宽度)
                this.$refs.sliderGroup.style.width = width + 'px'
            },

还有一个问题,在手机端点击的时候不能跳转,所以在初始化init_slider的时候将click:true去掉,bs中的click:true会阻止浏览器默认的click,自己派发一个click,这个click有恰好被fastclick监听到,然后被阻止,导致了click不能被执行,所以我们将bs中click:true去掉,因为a连接跳转就是一个默认行为,不需要监听click

切换tab时闪现,每次切换界面时都会重新发送数据请求,recommend的created生命周期等都会重新走一遍,重新获取数据,使用keeplive实现数据的缓存,

<template>
  <div id="app">
    <m-header></m-header>
    <tab></tab>
    <keep-alive>
       <router-view></router-view>
    </keep-alive>
    <player></player>
  </div>
</template>

slider被切走的时候会调用destroyed这个钩子函数,手动清除计时器等资源timer,有利于资源的释放

        destroyed() {
            clearTimeout(this.timer)
        }

timer是在自动轮播方法_play()中定义的

                this.timer = setTimeout(() => {
                        // goToPage是BScroll的方法,下标,方向,0代表x方向,间隔400
                        this.slider.goToPage(pageIndex, 0, 400)
                    }, this.interval)

轮播图完成,接下来是热门歌单部分的编写

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值