admin 管理员组

文章数量: 1184232

第5章 歌手页面开发

内容均为《Vue2.0开发企业级移动端音乐Web App 》学习笔记

包括歌手数据的抓取和处理、Singer 类的封装、类通讯录组件 listview开发和应用。

5-1 歌手页面布局和设计讲解

做一个类似于通讯录的功能,当滑动左边的列表,右侧的相应的字母分类加上选中的样式,当点击右侧的字母的时候,滑动到左侧相应的项,头部标题固定。

5-2 歌手数据接口抓取

src\api\singer.js

import {commonParams,options} from './config'
import jsonp  from 'common/js/jsonp.js'export function getsingerList(){
const url = '.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)
复制代码

在singer.vue中请求数据

created(){this._getsingerList()  
},
methods:{_getsingerList(){getsingerList().then(res=>{if(res.code===ERR_OK){console.log(res.data.list)//将数据做过滤,过滤出我们需要的数据,我们将前10当是热门的数据// this._normalizeSinger(res.data.list)}}).catch(err=>{console.log(err);})
},
复制代码

得到的数据如下

5-3 歌手数据处理和 Singer 类的封装

对歌手数据进行处理

 _normalizeSinger(list){//首先我们先对数据进行分类let map={hot:{title: HOT_NAME,items: []}}//首先我们先得到热门的歌曲,我们将得到的数据的前十条作为热门数据list.forEach((item,index) => {//在这里我们得到了热门的歌手if(index<HOT_SINGER_LEN){map.hot.items.push({id: item.Fsinger_mid,name: item.Fsinger_name,avatar: `${item.Fsinger_mid}.jpg?max_age=2592000`});}//在这里我们得到各个字母的的分类的歌手let key=item.Findex;if(!map[key]){//当没有当前的字母项,我们创建一个map[key]={title: key,items: []}}//把相应的项添加到map[key]中map[key].items.push({id: item.Fsinger_mid,name: item.Fsinger_name,avatar: `${item.Fsinger_mid}.jpg?max_age=2592000`})});}
复制代码

我们发现在push歌手信息的时候,avatar的计算是根据id而得到的,我们希望构造一个歌手类,通过传入id和name得到我们想要的数据格式。提高代码的复用性 common\js\singer.js

export default class Singer {constructor({name,id}){this.name=name,this.id=idthis.avatar = `${id}.jpg?max_age=2592000`}
}
复制代码

做调整

//引入歌手类文件
import Singer from 'common/js/singer'//对歌手数据进行处理_normalizeSinger(list){//首先我们先对数据进行分类let map={hot:{title: HOT_NAME,items: []}}//首先我们先得到热门的歌曲,我们将得到的数据的前十条作为热门数据list.forEach((item,index) => {//在这里我们得到了热门的歌手if(index<HOT_SINGER_LEN){map.hot.items.push(new Singer({id: item.Fsinger_mid,name: item.Fsinger_name,}));}//在这里我们得到各个字母的的分类的歌手let key=item.Findex;if(!map[key]){//当没有当前的字母项,我们创建一个map[key]={title: key,items: []}}//把相应的项添加到map[key]中map[key].items.push(new Singer({id: item.Fsinger_mid,name: item.Fsinger_name,}))});console.log(map);}
复制代码

我们发现这个顺序是杂乱无章的,我们其实希望的是按照顺序排列,并且数据的结构是一个数组,现在我们来处理一下这些数据。对上面的数据进行排序 热门>A>B>C...

let hot=[]
let ret=[]
//对数据进行分类,分成字母类数组和热门类数组
for(let key in 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);
复制代码

在_getsingerList()中得到ingerList

this.singerList=this._normalizeSinger(res.data.list)
复制代码

5-4 listview 基础组件的开发和应用-滚动列表实现

得到ingerList之后,我们就可以做滚动列表了,把data传入到scroll里面 引入scroll

import scroll from '../scroll/scroll'
复制代码
<template><scroll :data="data" class="listview"><ul><li class="list-group" v-for="(item,index) in data" :key="index"><h2 class="list-group-title">{{item.title}}</h2><ul><li class="list-group-item" v-for="singer in item.items" :key="singer.id"><img class="avatar" v-lazy="singer.avatar"><span class="name">{{singer.name}}</span></li></ul></li></ul></scroll>
</template>
复制代码

5-5 listview 基础组件的开发和应用-右侧快速入口实现(1)

布局

//计算属性中
shortcut(){得到title的集合数组,‘热门’取1个字let arr=[];this.data.forEach(item => {arr.push(item.title.substr(0,1));});return arr
}
复制代码
 <!-- 右侧点点部分--><div class="list-shortcut"><ul><li :data-index="index" class="item" v-for="(item,index) in shortcut" :key="index">{{item}}</li></ul></div>
复制代码

5-6 listview 基础组件的开发和应用-右侧快速入口实现(2)

触摸右侧标题 为了得到当前触摸的是哪一个点点,我们给加上了属性data-index

<div class="list-shortcut"><ul  @touchstart="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove"><li v-for="(item,index) in shortcut" :key="index":data-index="index" class="item":class="{current:index==currentIndex}">{{item}}</li></ul></div>
复制代码

当我们点击ul的时候我们需要获取到当前的点击元素的data-index来判断点击的是哪一个元素,那么怎么获取到data-index 这个属性呢,我们在dom,.js中export一个getData的方法。来设置或者获取属性

//给元素添加属性或者得到属性的值
export function getData(el,attrname,value){let prefix='data-'let  name=prefix+attrnameif(value){el.setAttribute(name,value);}else{return el.getAttribute(name);}
}
复制代码

在scroll组件中定义两个方法

  scrollTo() {// 滚动到指定的位置;这里使用apply 将传入的参数,传入到this.scrollTo()this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)},scrollToElement(){//滚动到指定的元素this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)}
复制代码

在listview的右侧字母列表中,绑定触摸事件。在触摸事件中记录开始的位置,和当前点击的位置。用this.touch来记录位置信息。 去到对应的项

onShortcutTouchStart(e) {let anchorIndex=getData(e.target,'index')//记录开始的位置this.touch.y1=e.touches[0].pageY;//开始的y坐标this.touch.anchorIndex=anchorIndex;//去到listHeigth数组的某一项this._scollTo(anchorIndex);
},
复制代码

listHeigth用来存 左侧的每一个项的 的clientHeight。这样我们可以计算当前的元素索引在左侧的哪个区间。

 watch: {data(){setTimeout(()=>{this.listHeigth=[];let height=0;this.listHeigth.push(0);let list=[...this.$refs.listGroup];for(let i=0;i<list.length;i++){height+=list[i].clientHeight;this.listHeigth.push(height);}},20)}},//滑动到哪里_scollTo(index){this.$refs.listview.scrollTo(0,-this.listHeigth[index],0);this.currentIndex=index;}复制代码

当手指滑动的时候,计算y2 和 y1的差值/字母块的高度,得到需要偏移多少个字母块,再加上当前的位置,得到需要滚动到的index,再滚动到相应的位置。 因为scroll本身可以滑动,当我们滑动右侧的时候,我们会阻止默认行为,阻止事件冒泡

@touchmove.stop.prevent="onShortcutTouchMove"
复制代码
//计算手机放下的位置,和move之后的位置,比较,让左边的滚动,用this.touch来记录位置信息onShortcutTouchMove(e){this.touch.y2=e.touches[0].pageY;//开始的y坐标let delta=(this.touch.y2-this.touch.y1)/CUTHEIGHT | 0//向下取整。获取到最接近的值let anchorIndex=parseInt(this.touch.anchorIndex)+deltathis._scollTo(anchorIndex);},
复制代码

5-7 listview 基础组件的开发和应用-右侧快速入口实现(3)

当我们滑动左侧的时候,我们需要监听我们当前滑动的纵向y的值,来判断当前滑动的是哪一个区间,来确定应该指向的索引 所以在scroll组件中,我们可以props一个listenScroll

listenScroll:{type:Boolean,default:false
}
复制代码

在_initScroll中去判断,当_initScroll为true的时候,即需要监听better-scroll的滚动时间,当为true的时候,需要派发一个滚动的事件,并且将相关数据emit给父组件。 在sroll.vue中并不需要写获取pos之后需要做的操作。sroll.vue只需要负责分发事件就可以。

 //判断是否需派发一个scroll事件if(this.listenScroll){//派发一个scroll事件let me=this;this.scroll.on('scroll',(pos)=>{//给父组件分发一个getScrollY事件。并且传递pos.y参数me.$emit('scroll',pos)})}
复制代码

在listview中确定是否需要listenScroll

created(){//当不需要渲染到dom上,不需要实时监听的,我们不必要放在data上this.listenScroll=true;//是否监听滚动事件this.listHeight=[]},
复制代码

在listview中定义scrollY,用来定义一个变量scrollY,实时记录歌手列表Y轴滚动的位置pos.y,

 data(){return{currentIndex:0,probeType:3,touch:{},//放右侧的字母的touchmove的位置信息scrollY:-1,diff: -1 //fixed title的偏移位置}},
复制代码

注意。scroll组件中设置了probeType的默认值为1:滚动的时候会派发scroll事件,会截流,只能监听缓慢的滚动,监听不到swipe快速滚动 解决:需要在中传递:probeType="3" 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件

在listview中绑定scroll事件

 <scroll class="listview" ref="listview" :data="data":probeType="probeType" :listenScroll="listenScroll"@scroll="scroll">...</scroll>
复制代码
 //接受到子组件的pos.yscroll(pos){this.scrollY=pos.y;//获取到y值。并且给了scrollY},
复制代码

注意:私有方法如_scrollTo()一般放在下面,公共方法或绑定事件的方法如scroll()放在上面

实时监听scrollY的变化,遍历listHeight得到每个group元素的【高度区间】上限height1和下限height2,对比scrollY和每个group元素的高度区间height2-height1,确定当前滚动位置currentIndex,映射到DOM中

 //监听scrollYDE变化scrollY(newY) {const listHeight = this.listHeightif(!listHeight){return}//当滚动到顶部,newY>0if(newY > 0) {this.currentIndex = 0return}//在中间部分滚动,遍历到最后一个元素,保证一定有下限,listHeight中的height比元素多一个for(let i = 0; i < listHeight.length-1; i++){let height1 = listHeight[i]let height2 = listHeight[i+1]//diff=下项+已经移动的距离//得到fixed title上边界距顶部的偏移距离 = 歌手列表title height下限 + newY(上拉为负值)this.diff = height2 + newY;if(-newY >= height1 && -newY < height2) { this.currentIndex = i//  console.log(this.currentIndex)return}}//当滚动到底部,且-newY大于最后一个元素的上限//currentIndex 比listHeight中的height多一个, 比元素多2个this.currentIndex = listHeight.length - 2},复制代码

5-8 listview 基础组件的开发和应用-右侧快速入口实现(4)

优化处理 touch事件都是加在父元素div class="list-shortcut"上的,点击头尾--“热”“Z”之前和之后的边缘区块,会发现也是可以点击的,但它没有对应显示的歌手列表,这个点击是没有意义的。touchmove一直在执行,这个事件一直没有结束,它的Y值就会变大,这样算出来的delta加上之前的touch.anchorIndex得到的值就可能会超 解决:

 //滑动到哪里_scollTo(index){//做边界处理//当点击的不是右侧的热门和字母块,if(!index && index !== 0){return}//当touchmove一直在执行if(index < 0){index = 0}else if(index > this.listHeight.length - 2){index = this.listHeight.length - 2}this.$refs.listview.scrollTo(0,-this.listHeight[index],0);this.currentIndex=index;},
复制代码

5-9 listview 基础组件的开发和应用-滚动固定标题实现(上)

当滚动列表,顶部的标题部分固定,展示当前滚动到的列表项的标题。当滚动到下一项时,展示到下一项的标题

  <!-- 头部固定处理 --><div class="list-fixed" v-show="fixedTitle" ref="fixedTitle"><div class="fixed-title">{{fixedTitle}}</div></div>
复制代码

当向下拉使得左侧列表往下移动的时候,我们希望fixedTitle是隐藏起来的,不能出现两个一样的标题。我们需要做边界处理,这种情况下的this.scrollY是大于0的

fixedTitle(){//做边界处理if(this.scrollY>0){return ''}return this.data[this.currentIndex] ?  this.data[this.currentIndex].title : ''
}复制代码
 .list-fixedposition: absolutetop: 0left: 0width: 100%.fixed-titleheight: 30pxline-height: 30pxpadding-left: 20pxfont-size: $font-size-smallcolor: $color-text-lbackground: $color-highlight-background
复制代码

5-10 listview 基础组件的开发和应用-滚动固定标题实现(下)

优化:歌手列表的title上边界滚动到fixed title下边界时,给fixed title添加一个上移效果,使两个title过渡顺滑 定义一个diff数据来记录偏移位置

 data(){return{currentIndex:0,probeType:3,touch:{},//放右侧的字母的touchmove的事件scrollY:-1,diff: -1 //fixed title的偏移位置}},
复制代码

在监听scrollY的时候实时得到diff diff=下项+已经移动的距离 得到fixed title上边界距顶部的偏移距离 = 歌手列下限+已经移动的距离newY(上拉为负值)

scrollY(newY) {const listHeight = this.listHeightif(!listHeight){return}//当滚动到顶部,newY>0if(newY > 0) {this.currentIndex = 0return}//在中间部分滚动,遍历到最后一个元素,保证一定有下限,listHeight中的height比元素多一个for(let i = 0; i < listHeight.length-1; i++){let height1 = listHeight[i]let height2 = listHeight[i+1]//diff=下项+已经移动的距离//得到fixed title上边界距顶部的偏移距离 = 歌手列表title height下限-已经移动的距离+ newY(上拉为负值)this.diff = height2 + newY;if(-newY >= height1 && -newY < height2) { this.currentIndex = i//  console.log(this.currentIndex)return}}//当滚动到底部,且-newY大于最后一个元素的上限//currentIndex 比listHeight中的height多一个, 比元素多2个this.currentIndex = listHeight.length - 2},复制代码

浮动的标题的高度

const TITLE_HEIGHT = 30;//浮动的标题的高度
复制代码

监听固定的标题的底部距离下一项的距离

  diff(newVal){//当距离大于0,并且当前的距离小于30,即下一项还没完全和固定标题粘合。let fixedTop=(newVal>0 && newVal<TITLE_HEIGHT) ?&emsp;newVal - TITLE_HEIGHT : 0if(this.fixedTop === fixedTop){return }this.fixedTop = fixedTopthis.$refs.fixedTitle.style.transform = `translate3d(0, ${fixedTop}px, 0)`
}
复制代码

5-11 loading组件

实时歌手列表的数据也是通过异步获取的,我们在此也需要加上一个loading的组件

  <!-- loading部分 -->
<div class="loading-container" v-show="!data.length"><loading></loading>
</div>
复制代码
.loading-containerposition: absolutewidth: 100%top: 50%transform: translateY(-50%)
</div>
复制代码

github chapter5

转载于:

本文标签: 第5章 歌手页面开发