zoukankan      html  css  js  c++  java
  • 基于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&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(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
    
     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
    
     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%)
    
     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
    
     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
    
     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(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
    
    }
    
    }
  • 相关阅读:
    家庭记账本_2
    家庭记账本_1
    安卓学习进度_25
    安卓软件学习进度_24
    对体温上报app的总结
    安卓软件学习进度_23
    安卓软件学习进度_22
    安卓开发
    安卓开发
    安卓开发
  • 原文地址:https://www.cnblogs.com/catbrother/p/9181041.html
Copyright © 2011-2022 走看看