基于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¬ice=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,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 } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |