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

来源:互联网 发布:可以赚钱的app软件 编辑:程序博客网 时间:2024/05/17 02:36

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(letkeyinmap) {
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(leti=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(leti=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
}
}

阅读全文
0 0
原创粉丝点击