vue 移动端音乐(3) >热门歌单推荐部分(webpack-dev-conf.js做后端接口代理+scroll插件)

1. 首先,获取歌单推荐部分的数据,与获取推荐数据不同,热门歌单数据的接口有host和referer的显示,我们的api请求被拒绝(500错误),必须要修改header,但是前端不能直接修改request header,我们采取后端接口代理的方法去解决。使用express,不是直接请求服务器的url,而是请求我们自己的server端,让local server再去请求QQ服务端

使用express启动代理服务器,原理:在getDiscList中不是直接请求url,而是请求express服务器端地址,再让我们的地址去请求服务端,使用nodejs请求服务器端,用到一个axios库,在浏览器端发送的是xmlhttprequest请求,在nodejs中发送的是http请求

我们使用axios请求服务器,在webpack-dev-conf.js中做如下配置

const express = require('express')
const axios = require('axios')
const app = express()
var apiRoutes = express.Router()
app.use('/api', apiRoutes)

在devServer{}中添加

before(app) {
      app.get('/api/getDiscList', (req, res) => {
        var 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 // 通过req从浏览器端发过来的一堆参数(platform,sin,ein等)透传给qq的服务端
        }).then((response)=>{ // qq服务端的响应数据,再通过res将响应数据输出到浏览器端
          res.json(response.data)
        }).catch((error)=>{
          console.log(error)
        })
      })
    }

之后,回到recommend.js中获取数据,请求的是本地express服务器的api数据(ajax请求),(本地express的数据是上边通过axios获得的)不是Jsonp数据了,返回的是axios的数据

import axios from 'axios'
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)
    })
}

2 将获取到的数据添加到DOM中,ul-li 并编写样式

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

先定义歌单的原始数据,并将其绑定到data中

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

 

 created() {
  //  setTimeout(() => {
  //    this._getRecommend() 
  //  }, 2000);
    this._getRecommend() 
    // 模拟loading效果
   // setTimeout(() => {
    //  this._getDiscList()
   // }, 1000);
      this._getDiscList()
  },
  methods: {
    _getRecommend() {
      getRecommend().then((res) => { // res对应的就是json对象
        if (res.code === ERR_OK) {
          // console.log(res.data.slider)
          this.recommends = res.data.slider // 根据fcp数据的格式返回的
        }
      })
    },
    _getDiscList() {
      getDiscList().then((res) => {
          if (res.code === ERR_OK) {
            // console.log(res.data.list)
            this.discList = res.data.list
          }
      })
    }

填充DOM

        <div class="recommend-list">
          <h1 class="list-title">热门歌单推荐</h1>
          <ul>
            <li v-for="item in discList" :key="item.id" class="item">
              <div class="icon">
                <img width="60" height="60" v-lazy="item.imgurl">
              </div>
              <div class="text">
                <h2 class="name" v-html="item.creator.name"></h2>
                <p class="desc" v-html="item.dissname"></p>
              </div>
            </li>
          </ul>
        </div>

3. 榜单优化:局部滚动,但是顶部不滚动,因为考虑到BScroll的复用性很大,我们将其抽象成一个组件,暂时初始化这些,如果有需要会在添加,在base下添加sroll组件

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

<script type="text/ecmascript-6">
  import BScroll from 'better-scroll'
  export default {
      props: {
            probeType: { // 监听滚动组价的种类
                type: Number,
                default: 1
            },
            click: { // 需不需要手动派发点击事件
                type: Boolean,
                default: true
            },
            data: {
                type: Array, // refresh 数据
                default: null
            },
            listenScroll: { // 要不要监听滚动事件
                type: Boolean,
                default: false
            }
      },
      mounted() { // 确保DOM被渲染
          setTimeout(() => {
              this._initScroll()
          }, 20)
      },
      methods: {
          _initScroll() {
              if (!this.$refs.wrapper) { // wrapper没有值的时候,直接return
                  return
              }
              this.scroll = new BScroll(this.$refs.wrapper, {
                  probeType: this.probeType,
                  click: this.click
              })
              // 如果要监听滚动事件,在初始化列BScroll之后要派发一个监听事件
              if (this.listenScroll) {
                  // BScroll 中的this是默认指向scroll的,所以要在me中保留vue实例的this
                  let me = this
                // 监听scroll,拿到pos后,派发一个函数将pos传出去
                this.scroll.on('scroll', (pos) => {
                    me.$emit('scroll', pos)
                })      
              }
          },
          enable() { // 代理方法,将scroll原生的方法都添加到我们生成的this.scroll实例上
              this.scroll && this.scroll.enable()
          },
          disable() {
              this.scroll && this.scroll.disable()
          },
          refresh() {
              this.scroll && this.scroll.refresh()
          },
          scrollTo() {
              this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) // applay引用到上下文中
          },
          scrollToElement() {
              this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
          }
      },
      // 要随时watch data变化时进行刷新
      watch: {
          data() {
             setTimeout(() => {
                 this.refresh()
             }, 20)
          }
      }
  }
</script>

<style scoped lang="stylus" rel="stylesheet/stylus">

</style>

4. 回到recommend.vue组件,注册并引入组件,在DOM中,我们为其添加一个内层元素div,一般在外层元素(<div class="recommend-content">)上引用ref,然后在内外层组件之间添加scroll组件,不要忘记将data传到组件中,这样data值发生变化的时候就会自动刷新了

  <div class="recommend">
     <scroll ref="scroll" class="recommend-content" :data="discList">
      <div>
        <div v-if="recommends.length" class="silder-wrapper">
          <slider>
            <div v-for="item in recommends" :key="item.id">
              <a :href="item.linkUrl">
                <img :src="item.picUrl" class="needsclick" @load="loadImage">
              </a>
            </div>
          </slider>
        </div>
        <div class="recommend-list">
          <h1 class="list-title">热门歌单推荐</h1>
          <ul>
            <li v-for="item in discList" :key="item.id" class="item">
              <div class="icon">
                <img width="60" height="60" v-lazy="item.imgurl">
              </div>
              <div class="text">
                <h2 class="name" v-html="item.creator.name"></h2>
                <p class="desc" v-html="item.dissname"></p>
              </div>
            </li>
          </ul>
        </div>
      </div>
      <div class="loading-container" v-show="!discList.length">
        <loading></loading>
      </div>
     </scroll>
  </div>

但是此时页面并没有滚动,mounted去初始化内容的时候页面还没有渲染,DOM被撑开时候要重新计算BScroll并refresh,才可以将BScroll正确的滚动,所以将data传入,watch观测到数据的变化,去refresh BScroll

recommend.vue中

<scroll ref="scroll" class="recommend-content" :data="discList">

在scroll.vue中,观测到data变化就去刷新bscroll

 // 要随时watch data的变化
      watch: {
          data() {
             setTimeout(() => {
                 this.refresh()
             }, 20)
          }
      }

5.recommend获取的过程是一个异步过程,图片的加载也是一个异步的过程,无法正确的获取到高度

 slider(轮播图)的高度不知何时会被撑开,是没有考虑到的,所以对img进行监听,若是加载到图片,就调用scroll.vue的refresh()方法刷新,重新计算高度,这样即使slider晚两分钟加载,也不会造成BScroll的出错,因为图片加载到之后就会重新计算属性

 <img :src="item.picUrl" class="needsclick" @load="loadImage">
    loadImage() {
      if (!this.checkloaded) { // 一旦有一张图片撑开了高度,slider的高度就会被撑开
        this.$refs.scroll.refresh() // 调用scroll的refresh方法
        this.checkloaded = true // 确保逻辑只执行一次,因为只要有一张图片加载完成就会撑开高度
      }
    }

6. 为节省资源,才用图片懒加载,使用VueLazyload插件,npm安装之后,在main.js中注册,提供一张默认的图片

import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
  loading: require('common/image/default.png')
})

在recommend.vue中,将:src="imgUrl" 换成 v-lazy ="imgUrl"

<img width="60" height="60" v-lazy="item.imgurl">

在轮播图的图片显示部分添加needsclick,使图片可以被点击

<img :src="item.picUrl" class="needsclick" @load="loadImage">

7. 开发loading组件

<template>
  <div class="loading">
    <img width="24" height="24" src="./loading.gif">
    <p class="desc">{{title}}</p>
  </div>
</template>
<script type="text/ecmascript-6">
  export default {
    props: {
      title: {
        type: String,
        default: '正在载入...'
      }
    }
  }
</script>
<style scoped lang="stylus" rel="stylesheet/stylus">
  @import "~common/stylus/variable"

  .loading
    width: 100%
    text-align: center
    .desc
      line-height: 20px
      font-size: $font-size-small
      color: $color-text-l
</style>

在recommend.vue中引用loading.vue,歌单没有数据时会出现loading

      <div class="loading-container" v-show="!discList.length">
        <loading></loading>
      </div>

 

.loading-container
        position: absolute
        width: 100%
        top: 50%
        transform: translateY(-50%)

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值