github:https://github.com/Ching-Lee/vue-music
1.分析后台数据
从QQ音乐网页版获取后台数据
这里的回掉函数是callback
-
创建歌手页面的请求文件
import jsonp from '../assets/js/jsonp'
import {commonParams, optionsPc} from './config'
export default function getSingerList () {
const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
const data = {
'comm': {
'ct': 24,
'cv': 10000
},
'singerList': {
'module': 'Music.SingerListServer',
'method': 'get_singer_list',
'param': {
'area': -100,
'sex': -100,
'genre': -100,
'index': -100,
'sin': 0,
'cur_page': 1
}
}
}
// 实现将多个对象拷贝到同一个对象中
const param = Object.assign({}, commonParams,
{
loginUin: 0,
hostUin: 0,
format: 'jsonp',
platform: 'yqq',
needNewCode: 0,
data: JSON.stringify(data)
})
// 返回值就是promise
return jsonp(url, param, optionsPc)
}
- 在config.js中一些参数做了改变
// 配置通用参数
export const commonParams = {
g_tk: 5381,
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0
}
// 配置jsonp库的通用的options
export const options = {
// 通过qq得到了回掉函数的参数名
param: 'jsonpCallback'
}
// PC端的回掉函数
export const optionsPc = {
// 通过qq得到了回掉函数的参数名
param: 'callback'
}
- singer.vue组件中获取数据
<script type="text/ecmascript-6">
import getSingerList from '../../api/singer'
export default {
data () {
return {
singerlist: []
}
},
created () {
this._getSingerList()
},
methods: {
_getSingerList () {
getSingerList().then((result) => {
this.singerlist = result.singerList.data.singerlist
}, (err) => { console.log(err) }
)
}
}
}
2.我们将得到的数据根据country聚类
- singer.vue中添加方法
_singerCountryMap () {
// 将数据按照地点区分
let map = {}
for (let value of this.singerlist) {
let key = value.country
if (!map[key]) {
let item = []
map[key] = item
}
map[key].push(new Singer(value))
}
return map
}
}
-
Singer类里面存储了和歌手相关的信息,图片的地址是根据singer_mid得到的
export default class Singer {
constructor (value) {
this.country = value.country
this.singer_id = value.singer_id
this.name = value.singer_name
this.singer_pic = 'http://y.gtimg.cn/music/photo_new/T001R150x150M000' + value.singer_mid + '.jpg?max_age=2592000'
}
}
3.创建listView组件
遍历data对象,对于每一个城市的键值对是一个li
然后在li中又嵌套遍历该城市的value值(是该城市的歌手的数组)。
<template>
<ul>
<li v-for="(value, key, index) in data" v-bind:key="index">
<h2 class="title">{{key}}</h2>
<ul>
<li v-for="(item, index) in (value)" v-bind:key="index" class="singer_item">
<img v-bind:src="item.singer_pic" class="singerPic">
<span class="singer_name">{{item.name}}</span>
</li>
</ul>
</li>
</ul>
</template>
<script type="text/ecmascript-6">
export default {
props: {
data: Object,
default: null
}
}
</script>
<style>
.title{
height: 2rem;
background-color: darkorange;
color: whitesmoke;
padding: 0.25rem 1rem;
line-height: 2rem;
margin-bottom: 0.5rem;
}
.singerPic{
width: 5rem;
border-radius: 50%;
}
.singer_item{
padding: 0.5rem 1rem;
position:relative;
}
.singer_name{
color: white;
margin-left: 2rem;
position: absolute;
bottom: 50%;
transform: translate(0,50%);
}
</style>
- 在singer.vue中调用该组件
<template>
<div class="singer">
<listview v-if="singerlist.length" v-bind:data=" _singerCountryMap ()"></listview>
</div>
</template>
<style>
.singer{
background-color: orange;
}
</style>
3.图片懒加载
我们现在是一次性加载所有的图片,会影响性能,这里应该使用图片懒加载
安装vue-lazyload插件
在main.js中引入vue.lazyload
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad, {
loading:require('./assets/images/music_logo.png')
})
就会进行首屏加载,之后滚动到要显示的地方会再加载。
在listveiw中更改,使用v-lazy标签
<ul>
<li v-for="(item, index) in (value)" v-bind:key="index" class="singer_item">
<img v-lazy="item.singer_pic" class="singerPic">
<span class="singer_name">{{item.name}}</span>
</li>
</ul>
4.正在载入loading组件
<template>
<div class="loading">
<img src="./loading.gif">
<p class="dec">{{title}}</p>
</div>
</template>
<script type="text/ecmascript-6">
export default {
props: {
title: {
type: String,
default: '正在载入...'
}
}
}
</script>
<style>
.loading{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
text-align: center;
}
.loading p{
font-size: 14px;
}
</style>
在歌手组件中调用loading组件,使用v-show,在列表没有长度的时候显示,有长度不显示
<template>
<div>
<div class="singer" v-if="singerlist.length">
<listview v-bind:data=" _singerCountryMap ()"></listview>
</div>
<div v-show="!singerlist.length">
<loading></loading>
</div>
</div>
</template>
5.快速导航入口
- 首先添加计算属性,获取到所有城市的名称:
computed: {
shortcutList () {
let keylist = []
for (let key in this.data) {
if (key) {
keylist.push(key)
}
}
return keylist
}
},
- 在template中添加快速入口的div
<div v-if="data">
<ul ref="quickNav" class="shortpart" @click="onShortcutTouchStart">
<li v-for="(key,index) in shortcutList" v-bind:key="index" class="shortitem" v-bind:data-index="index">
{{key}}
</li>
</ul>
</div>
mounted () {
this.$nextTick(function () {
this.citylist = this.$refs.roll.children
this.headerHeight = this.citylist[0].offsetTop
this.quicknavlist = this.$refs.quickNav.children
this.scrollListener()
})
},
整个ul使用了固定定位。
<style>
.title{
height: 2rem;
background-color: darkorange;
color: whitesmoke;
padding: 0.25rem 1rem;
line-height: 2rem;
margin-bottom: 0.5rem;
}
.singerPic{
width: 5rem;
border-radius: 50%;
}
.singer_item{
padding: 0.5rem 1rem;
position:relative;
}
.singer_name{
color: white;
margin-left: 2rem;
position: absolute;
bottom: 50%;
transform: translate(0,50%);
}
.shortpart{
position: fixed;
top:50%;
right:0;
transform: translate(0,-25%);
width:4rem;
text-align: center;
}
.shortitem{
margin: 1rem 0;
color: black;
font-size: 12px;
}
</style>
- 可以看到给ul注册了点击事件,使用了事件委托的原理。
- scrollTo的意思就是把传入参数的x,y坐标移动到浏览器(0,0)点。
- offsetLeft 和 offsetTop 返回的是相对于 offsetParent 元素的距离,而 offsetParent 指的是一个元素最近的父级定位元素,如果没有定位元素就是文档根节点。所以会超出视窗
点击了之后通过event.target拿到被点击元素的li,获取到data-index属性,然后去遍历左边的城市大的li,如果这个li的索引和data-index相同,就去计算出当前这个li距离可视窗口顶部的距离,然后减去头部和导航栏的距离,就是让这个计算出的高度滚动到(0,0)点。
methods: {
onShortcutTouchStart (event) {
// 点击的快速入口的li
let current = event.target
// 拿到点击的索引
let index = current.getAttribute('data-index')
// 遍历各个城市的li(每个li里面嵌套了title和ul(里面是该城市的歌手))
for (let liIndex in this.citylist) {
// 如果点击的这个快速入口的索引和
if (liIndex === index) {
let height = this.citylist[liIndex].offsetTop - this.headerHeight
window.scrollTo(0, height)
}
}
},
- 之后我们添加一个滚动监听事件,看各个城市的标题出现在屏幕中,我们就让快速导航栏颜色变白,同时他的前一个或者后一个如果是白的,就让他变黑并break。
这里用到了事件节流
scrollListener () {
let _self = this
let timeout
window.addEventListener('scroll', function () {
if (timeout) {
clearTimeout(timeout)
}
// 事件节流
timeout = setTimeout(callback(_self), 100)
})
function callback (_self) {
for (let index = 0; index < _self.citylist.length; index++) {
// 把标题那一行给拿出来,h2
let title = _self.citylist[index].getElementsByTagName('h2')[0]
let titleTop = title.offsetTop - (document.body.scrollTop || document.documentElement.scrollTop)
let currentli = _self.quicknavlist[index]
if (currentli) {
if (titleTop >= _self.headerHeight && titleTop <= document.documentElement.clientHeight) {
currentli.style.color = 'white'
if (_self.quicknavlist[index - 1].style.color === 'white') {
_self.quicknavlist[index - 1].style.color = 'black'
}
if (_self.quicknavlist[index + 1].style.color === 'white') {
_self.quicknavlist[index + 1].style.color = 'black'
}
break
}
}
}
}
},