github:https://github.com/Ching-Lee/vue-music
1.分析后臺數(shù)據(jù)
從QQ音樂網(wǎng)頁版獲取后臺數(shù)據(jù)


這里的回掉函數(shù)是callback
-
創(chuàng)建歌手頁面的請求文件
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
}
}
}
// 實現(xiàn)將多個對象拷貝到同一個對象中
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中一些參數(shù)做了改變
// 配置通用參數(shù)
export const commonParams = {
g_tk: 5381,
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0
}
// 配置jsonp庫的通用的options
export const options = {
// 通過qq得到了回掉函數(shù)的參數(shù)名
param: 'jsonpCallback'
}
// PC端的回掉函數(shù)
export const optionsPc = {
// 通過qq得到了回掉函數(shù)的參數(shù)名
param: 'callback'
}
- singer.vue組件中獲取數(shù)據(jù)
<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.我們將得到的數(shù)據(jù)根據(jù)country聚類

要實現(xiàn)的效果
- singer.vue中添加方法
_singerCountryMap () {
// 將數(shù)據(jù)按照地點區(qū)分
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類里面存儲了和歌手相關(guān)的信息,圖片的地址是根據(jù)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.創(chuàng)建listView組件

遍歷data對象,對于每一個城市的鍵值對是一個li
然后在li中又嵌套遍歷該城市的value值(是該城市的歌手的數(shù)組)。
<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中調(diào)用該組件
<template>
<div class="singer">
<listview v-if="singerlist.length" v-bind:data=" _singerCountryMap ()"></listview>
</div>
</template>
<style>
.singer{
background-color: orange;
}
</style>
3.圖片懶加載
我們現(xiàn)在是一次性加載所有的圖片,會影響性能,這里應(yīng)該使用圖片懶加載
安裝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>
在歌手組件中調(diào)用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.快速導(dǎo)航入口

- 首先添加計算屬性,獲取到所有城市的名稱:
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的意思就是把傳入?yún)?shù)的x,y坐標移動到瀏覽器(0,0)點。
- offsetLeft 和 offsetTop 返回的是相對于 offsetParent 元素的距離,而 offsetParent 指的是一個元素最近的父級定位元素,如果沒有定位元素就是文檔根節(jié)點。所以會超出視窗
點擊了之后通過event.target拿到被點擊元素的li,獲取到data-index屬性,然后去遍歷左邊的城市大的li,如果這個li的索引和data-index相同,就去計算出當前這個li距離可視窗口頂部的距離,然后減去頭部和導(dǎo)航欄的距離,就是讓這個計算出的高度滾動到(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)
}
}
},
- 之后我們添加一個滾動監(jiān)聽事件,看各個城市的標題出現(xiàn)在屏幕中,我們就讓快速導(dǎo)航欄顏色變白,同時他的前一個或者后一個如果是白的,就讓他變黑并break。
這里用到了事件節(jié)流
scrollListener () {
let _self = this
let timeout
window.addEventListener('scroll', function () {
if (timeout) {
clearTimeout(timeout)
}
// 事件節(jié)流
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
}
}
}
}
},

