歌手页面类似于通讯录,相应的功能有:获取数据、排序、快速切换、左右联动、滚动等

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
        }
      }
    },
······

云深不知处
431 声望10 粉丝