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%)