歌手页面类似于通讯录,相应的功能有:获取数据、排序、快速切换、左右联动、滚动等
1.获取数据
在api/singer.js中导入JSONP方法,获取singers,在singer.vue中
created() {
this._getSingerList()
},
_getSingerList() {
getSingerList().then((res) => {
if (res.code === ERR_OK) {
console.log(res.data.list)
//序列化数据
this.singers = this._normallizeSinger(res.data.list)
}
})
},
得到的数据结构入如下:
2.数据序列化
这样结构不符合我们的需求,需要序列化;
在序列化时候,相应的分类中push是singer实例,目的是生成avatar
_normallizeSinger(list) {
//1.定义一个map
let map = {
hot: {
title: HOT_NAME,
items: []
}
}
//2.将前 HOT_SINGER_LEN = 10个放入‘热’分类中
list.forEach((item, index) => {
if (index < HOT_SINGER_LEN) {
map.hot.items.push(new Singer({
id: item.Fsinger_mid,
name: item.Fsinger_name
}))
}
//3.其他的按照Findex分类
const key = item.Findex //A B C D...
if (!map[key]) {
map[key] = {
title: key,
items: []
}
}
map[key].items.push(new Singer({
id: item.Fsinger_mid,
name: item.Fsinger_name
}))
})
//4.为了得到有序列表,我们需要排序map,定义一个hot一个其他ret
let hot = []
let ret = []
for (let key in map) {
let val = map[key]
//如果是A B C等在push到ret其他
if (val.title.match(/[a-zA-Z]/)) {
ret.push(val)
//如果是‘热’HOT_NAME则放入hot
} else if (val.title === HOT_NAME) {
hot.push(val)
}
}
//5.对其他ret进行排序
ret.sort((a, b) => {
return a.title.charCodeAt(0) - b.title.charCodeAt(0)
})
//6.合并两个数组
return hot.concat(ret)
},
3.singer实例化
在序列化时候,相应的分类中push是singer实例,目的是生成avatar
export default class Singer {
constructor ({id, name}) {
this.id = id
this.name = name
this.avatar = `http://y.gtimg.cn/music/photo_new/T001R150x150M000${id}.jpg?max_age=2592000`
}
}
4.样式
这里要进行滚动,所以使用了scroll.vue组件
5.数据序列化后传入list-vue
<div class="singer" ref="singer">
<list-view ref="list" @select="selectSinger" :data="singers"></list-view>
<router-view></router-view>
</div>
通过:data传入this.singers,同时,emit事件selectSinger,导航至singer-detail.vue中
selectSinger(singer) {
this.$router.push({
path: `/singer/${singer.id}`
})
//同时,将singer的信息放入vuex中
this.setSinger(singer)
},
6.list-vue 右→左联动
将this.singer传入list-vue中,数据的渲染左侧歌手列表,右侧导航索引
6.1右侧导航:索引数据
利用计算属性,获取右侧导航数据,然后渲染
computed: {
shortcutList() {
return this.data.map((group) => {
return group.title.substr(0, 1)
})
},
......
},
6.2右侧导航:定义touch事件
<!--touchumove.stop.prevent 阻止事件冒泡、默认行为(浏览器滚动)-->
<div class="list-shortcut" @touchstart="onShortCutTouchStart" @touchmove.stop.prevent="onShortCutTouchMove">
<ul>
<li :key="item.title" v-for="(item,index) in shortcutList"
:data-index="index" //自定义属性data-index用来获取当前li的索引值
:class="{'current':currentIndex === index}"
class="item">{{item}}</li>
</ul>
</div>
牵涉到滚动事件,同样的思路:在created中创建this.touch = {}
onShortCutTouchStart(e) {
let anchorIndex = getData(e.target, 'index') //getData公共方法,获取当前点击的li的索引
let firstTouch = e.touches[0]
this.touch.y1 = firstTouch.pageY
this.touch.anchorIndex = anchorIndex
this._scrollTo(anchorIndex)
},
onShortCutTouchMove(e) {
let firstTouch = e.touches[0]
this.touch.y2 = firstTouch.pageY
//获取相应的滑动了几个li的高度ANCHER_HEIGHT=18
let delta = (this.touch.y2 - this.touch.y1) / ANCHER_HEIGHT | 0
//根据计算的滑动几个li数量,得出索引值
let anchorIndex = parseInt(this.touch.anchorIndex) + delta
//左侧根绝索引值进行滚动
this._scrollTo(anchorIndex)
},
_scrollTo(index) {
//点击最顶部或者底部的时候,因为时间在div上,而div{padding:20px 0},所以,这个时候没有点击到li元素,也就无法获取index,因此index:null
if (!index && index !== 0) {
return
}
//滑动到最顶部或者底部
if (index < 0) {
index = 0
} else if (index > this.listHeight.length - 2) {
//this.listHeight.length - 2:正常情况index=this.listHeight.length-1,这里是因为在初始化this.listHeight的方法_caculateHeight中push了一个height = 0
index = this.listHeight.length - 2
}
//0:是表示缓动动画时间 这里表示立刻滚动没有动画
//调用scroll组件的方法
this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0)
//scrollY:左→右联动的滑动距离
this.scrollY = -this.listHeight[index]
},
7. 左→右联动
7.1 组件有数据的时候就开始计算高度
当传入数据的时候,开始计算高度:data="singers"同touch一样在created中定义this.listHeight=[]共享不用双向绑定的数据
watch: {
data() {
setTimeout(() => {
this._caculateHeight()
}, 20)
},
····
}
####7.2 将左侧列表高度叠加放入一个数组
methods:{
_caculateHeight() {
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)
}
}
}
7.3 监听左侧滚动
牵涉到滚动,我们就需要使用scroll组件
在methods中定义scroll事件
scroll(pos) {
//pos.y 滑动时负数,到顶部的时候,向下滑动空白回弹,这时是正数
this.scrollY = pos.y
},
在watch中观察scrollY
watch: {
·······
scrollY(newY) {
const listHeight = this.listHeight
//到顶部的时候,向下滑动空白回弹,这时是正数,newY>0
if (newY > 0) {
//this.currentIndex给左侧导航高亮
this.currentIndex = 0
return
}
//滚动至中部
for (let i = 0; i < listHeight.length - 1; i++) {
let height1 = listHeight[i]
let height2 = listHeight[i + 1]
if (-newY >= height1 && -newY < height2) {
//滚动左侧,从而找到相应的索引,右侧导航高亮
this.currentIndex = i
//比如是A上限距离顶部的距离 newY是个负值,相当于减
this.diff = height2 + newY
return
}
}
//滚动至底部,且-newY大于最后一个元素的上限 因为listHeight首先push一个height=0
this.currentIndex = listHeight.length - 2
},
······
8. 左侧列表的过渡效果
.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
在data中定义{diff:-1}
在watch中观察diff
diff(newVal) {
//newVal - TITLE_HEIGHT为负值,因为transform往上是负值
//当newVal<TITLE_HEIGHT(title的高度30)的时候,fixedTop != 0 这个时候继续执行
let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0
if (this.fixedTop === fixedTop) {
return
}
this.fixedTop = fixedTop
//继续执行,fixed上移
this.$refs.fixed.style.transform = `translate3d(0, ${fixedTop}px, 0)`
}
同时在scrollY的watch中计算diff
watch: {
·······
scrollY(newY) {
......
for (let i = 0; i < listHeight.length - 1; i++) {
let height2 = listHeight[i + 1]
if (-newY >= height1 && -newY < height2) {
......
this.diff = height2 + newY
}
}
},
······
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。