基于Vue2.0的音乐播放器(2)——歌手模块

1、分析设计稿

顶部:标题  

左部:歌手列表

右部:快速入口——A-Z的排序切换

数据:动态获取

2、数据获取及处理

2-1:数据

接口获取——https://y.qq.com/portal/singer_list.html

从控制台network界面上,js对应模块找到

fcg-bin/v8.fcg?channel=singer&page=list&key=all_all_all&pagesize=100&pagenum=1&g_tk=5381&jsonpCallback=GetSingerListCallback&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0

若要使得json格式文件进行可视化处理,需要下载jsonView插件,详情可见博文:

http://blog.csdn.net/zxy9602/article/details/78698169,

安装完插件,并在chrome浏览器上进行加载,可以得到如下效果:

之后再进行配置,进行相关设置,对应console控制台下的Headers目录进行相关查看参数配置

在项目工程api模块定义一个singer.js文件,用于设置与后端交互的接口,从而加载。

由于采用的是jsonp为主载的方式,且加载的第三方都是https://y.qq.com,所以所对应的公共参数相同

import jsonpfrom'common/js/jsonp'

import { commonParams,options }from'./config'


export functiongetSingerList() {

const url ='https://c.y.qq.com/v8/fcg-bin/v8.fcg'

const data =Object.assign({},commonParams, {

channel: 'singer',

page: 'list',

key: 'all_all_all',

pagesize: 100,

pagenum: 1,

hostUin: 0,

needNewCode: 
0,

platform: 'yqq',

g_tk: 1664029744

})


return jsonp(url,data,options)

}

2-2:歌手数据处理

首先,对歌手数据进行处理,其次对数据进行歌手类的封装聚合

2-2.1:获取歌手数据、进行规范化歌手数据、按照/a-zA-Z/进行排序

<scripttype="text/ecmascript-6">

import {getSingerList}from'api/singer'

import {ERR_OK}from'api/config'

import Singerfrom'common/js/singer'


const HOT_NAME ='热门'

const HOT_SINGER_LEN =10


export default {

data() {

return {

// Object

singers: []

}

},

created() {

this._getSingerList()

},

methods: {

// 获取歌手数据

_getSingerList() {

// then()表示promise成功

getSingerList().then((res)=> {

if(res.code ===ERR_OK) {

this.singers =res.data.list

console.log(this._normalizeSinger(this.singers))

}

})

},

// 规范化歌手数据

_normalizeSinger(list) {

// 首先遍历数据

let map = {

// 热门数据

hot: {

title: HOT_NAME,

items: []

}

}

list.forEach((item,index)=> {

if( index <HOT_SINGER_LEN){

// 添加至热门数据

// 由constructor构造器Singer对象,直接引用singer.js

map.hot.items.push(newSinger({

id: item.Fsinger_mid,

name: item.Fsinger_name,

}))

}

// 给list做聚类

const key =item.Findex

if(!map[key]) {

map[key] = {

title: key,

items: []

}

}

map[key].items.push(newSinger({

id: item.Fsinger_mid,

name: item.Fsinger_name

}))

})

// 为了得到有序列表,我们需要处理map

let hot = []

let ret = []

for(letkeyin
map) {

let val =map[key]

if(val.title.match(/[a-zA-Z]/))
 {

ret.push(val)

}else if(val.title ===HOT_NAME) {

hot.push(val)

}

}

// 字母排序

ret.sort((a,b)=> {

return a.title.charCodeAt(0) -b.title.charCodeAt(0)

})

// 得到一个一维数组

return hot.concat(ret)

}

}

}

</script>

2-2.2:歌手Singer类的封装聚合

由于在map.hot.items()热门模块、map[key].items()对应的A-Z模块,都是将Singer类的配置文件,push()添加到map里边,所以可以将Singer作为一个类进行封装聚合,从而以对象的形式进行调用。

// Singer做聚合,给对应的singer.vue list表单,定义一个Singer类,以对象形式引用

export defaultclassSinger {

constructor({ 
id, name }) {

this.id =id

this.name =name

this.avatar =`https://y.gtimg.cn/music/photo_new/T002R300x300M000${id}.jpg?max_age=2592000`

}

}

效果展示:

3、左侧——listview.vue电话簿组件

该模块主要用于加载歌手列表界面,由于需要滚动,所以需要Scroll组件的使用

import Scroll from "base/scroll/scroll"

<template>

<scrollclass="listview":data="data">

<ul>

<li 
v-for="(group,index)indata"
class="list-group":key="index">

<h2 
class="list-group-title">{{group.title}}</h2>

<ul>

<li 
v-for="(item,index)ingroup.items"class="list-group-item":key="index">

<img 
class="avatar" 
:src="item.avatar">

<span 
class="name">{{item.name}}</span>

</li>

</ul>

</li>

</ul>

</scroll>

</template>


<scripttype="text/ecmascript-6">

import Scrollfrom'base/scroll/scroll'


export default {

props: {

data: {

type: Array,

default: []

}

},

components: {

Scroll

}

}

</script>


<stylelang="stylus"scoped>

@import "~common/stylus/variable"


.listview 

position: relative

width: 100%

height: 100%

overflow: hidden

background: $color-background

.list-group

padding-bottom: 30px

.list-group-title

height: 30px

line-height: 30px

padding-left: 20px

font-size: $font-size-small

color: $color-text-l

background: $color-highlight-background

.list-group-item

display: flex

align-items: 
center

padding: 20px 0 0 30px

.avatar

width: 50px

height: 50px

border-radius: 50%

.name

margin-left: 20px

color: $color-text-l

font-size: $font-size-medium

.list-shortcut

position: absolute

z-index: 30

right: 0

top: 50%

transform: translateY(-50%)

width: 20px

padding: 20px 0

border-radius: 10px

text-align: center

background: $color-background-d

font-family: 
Helvetica

.item

padding: 3px

line-height: 1

color: $color-text-l

font-size: $font-size-small

&.current

color: $color-theme

.list-fixed

position: absolute

top: 0

left: 0

width: 100%

.fixed-title

height: 30px

line-height: 30px

padding-left: 20px

font-size: $font-size-small

color: $color-text-l

background: $color-highlight-background

.loading-container

position: absolute

width: 100%

top: 50%

transform: translateY(-50%)

</style>

4、右侧——快速入口的实现(listview.vue)

<!-- 右侧:快速入口/a-zA-Z/ 离开时候需要阻止事件冒泡-->

<div 
class="list-shortcut" 
@touchstart="onShortcutTouchStart" 
@touchmove.stop.prevent="onShortcutTouchMove">

<ul>

<li 
v-for="(item, 
index) in shortcutList"

class="item"

:data-index="index"
:key="item.key"

:class="{'current' :
currentIndex===index}"

>

{{item}}

</li>

</ul>

</div>

<div 
class="list-fixed" 
v-show="fixedTitle" 
ref="fixed">

<h1 
class="fixed-title">{{fixedTitle}}</h1>

</div>

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

<loading></loading>

</div>

  实现流程:

(1)首先,定义锚点个数,即抓取到“A-Z”歌手字母开头总数“18”

(2)其次,定义滚动scroll的歌手group的数据data,以及将要触发的位置

(3)定义两个事件,手指触摸开始+离开,同时设置每次均是“滚动标题”至上原则

(4)通过计算高度变化,以及定义变量diff高度差的方式,来设置元素的偏移

(5)以“当前触摸元素索引”currentIndex,来设置左边“歌手列表”与右边字母索引“a-z”之间的匹配,完成联动的效果。

运行效果图如下:

const ANCHOR_HEIGHT =
18

const TITLE_HEIGHT =
30


export default {

created() {

// 创建一个touch空对象

this.touch = {}

// 创建一个监听scroll事件

this.listenScroll =
true

this.listHeight = []

this.probeType =
3

},

data() {

return {

scrollY: -1,

// 当前滚动到的位置

currentIndex: 
0,

// 滚动的上限与下限的滚动差

diff: -1

}

},

props: {

data: {

type: Array,

default: []

}

},

computed: {

// 右侧快速入口

shortcutList() {

return this.data.map((group)
=> {

return group.title.substr(0,
1)

})

},

// 滚动标题至上

fixedTitle() {

if(this.scrollY >
0) return

return this.data[this.currentIndex] ?
this.data[this.currentIndex].title :
''

}

},

methods: {

onShortcutTouchStart(e) {

// 获取当前触摸的index

let anchorIndex =
getData(e.target,
'index')

// 第一次触发时的位置

let firstTouch =
e.touches[0]

// 获取touch到的垂直方向位置

this.touch.y1 =
firstTouch.pageY

// 记录下来需要锚点的index

this.touch.anchorIndex =
anchorIndex

// 引用listview元素,进行滚动 

this._scrollTo(anchorIndex)

},

// 触发离开

onShortcutTouchMove(e) {

let firstTouch =
e.touches[0]

this.touch.y2 =
firstTouch.pageY

// 定义需要滚动对少个data元素,|0表示取整,类似于Math.floor()

let delta = (this.touch.y2 -
this.touch.y1) /
ANCHOR_HEIGHT | 0

// 离开move时候的anchorIndex,由于this.touch.anchorIndex为字符串类型,因此要转换为整型int

let anchorIndex =
parseInt(this.touch.anchorIndex) +
delta

this._scrollTo(anchorIndex)

},

// scroll()

scroll(pos) {

// 实时观测滚动到y轴的距离

this.scrollY =
pos.y

},

// 滚动到哪个索引的元素的位置

_scrollTo(index) {

// 若index === null,返回null

if(!index &&
index !== 0){

return 

}

// 若index<0 || index> this.listHeight-2

if(index <
0) {

index = 0

} else if(index >
this.listHeight.length -2) {

index = this.listHeight.length -2

}


// 手动设置scrollY的位置

this.scrollY = -this.listHeight[index]

this.$refs.listview.scrollToElement(this.$refs.listGroup[index],
0)

},

// 计算高度

_calculateHeight() {

this.listHeight = []

const list =
this.$refs.listGroup

let height =
0

this.listHeight.push(height)

for(let
i=0; 
i<list.length;
i++) {

let item =
list[i]

height += item.clientHeight

this.listHeight.push(height)

}

}

},

watch: {

// 监听data发生变化

data() {

setTimeout(() 
=> {

this._calculateHeight()

}, 20)

},

// 监听scrollY的变化

scrollY(newY) {

const listHeight =
this.listHeight

// 当滚动到顶部,newY>0

if(newY >
0) {

this.currentIndex =
0

return 

}

// 当中间部分滚动, 

for(let
i=0; 
i<listHeight.length -1;
i++) {

let height1 =
listHeight[i]

let height2 =
listHeight[i+1]

// 向上滚动srcollY的值为负 所以加上负号

// 若不是height2下限,且在height1与height2之间

if(-newY >=
height1 && -newY<height2) {

this.currentIndex =
i

// 设置diff

this.diff =
height2 + newY

// console.log(this.diff)

// console.log(this.currentIndex)

return 

}

}

// 当滚动到底部,且-newY大于最后一个元素的上限

this.currentIndex =
listHeight.length -2

},

// 实时变化的newVal

diff(newVal) {

let fixedTop = (newVal >
0 && newVal<
TITLE_HEIGHT) ? newVal-TITLE_HEIGHT :
0

if (this.fixedTop ===
fixedTop) {

return 

}

this.fixedTop =
fixedTop

// 设置元素的偏移

this.$refs.fixed.style.transform =
`translate3d(0, ${fixedTop}px, 0)`

}

},

components: {

Scroll,

Loading

}

}

猜你喜欢

转载自www.cnblogs.com/catbrother/p/9181041.html