Vue2音乐播放器分析

本文详细介绍了如何使用Vue2构建一个音乐播放器应用,包括App.vue的结构,底部导航栏的设置,main.js中引入的依赖,以及商城骨架的搭建。在分类主页面设计中,采用一级和二级分类,利用路由变化控制导航栏显示。进一步讲解了Jsonp解决跨域问题,歌手列表页的实现,包括Listview组件的使用,图片懒加载,以及快速入口的实现。还涉及到了子路由的配置,Vuex状态管理和数据处理。在歌手详情页,通过定义music-list组件展示歌曲列表,实现了完整的功能流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

App.vue

和常规一样只包含#app标签,再加上渲染命令router-view,但是这里增加了底部导航栏 NavBottomView, css格式文件为index.css。需要注意的是启用了watch属性来坚挺路由的变化,并通过路由变化来设置底部导航栏是否显示。

watch: {
      $route(to,from){
        if(to.path.indexOf('detail')!==-1){
          this.$store.dispatch('hideNav');
          console.log(to.path.indexOf('detail'),"-----");
        }else{
          this.$store.dispatch('showNav');
          console.log(to.path.indexOf('detail'),"==========");
        }
        if(to.path === '/cart' || to.path === '/search' || to.path === '/login' || to.path === '/register'){
          this.$store.dispatch('hideNav');
        }
      }
    },

main.js

导入 vue-router,axios, router.config,lazyload等,加载全局css 文件。

分类主页面

目前只有一级分类,考虑到流行电商App都不搞成树状菜单,所以考虑把支持2级分类,第一级分类在左边以菜单栏方式显示,2级分类显示在右边,如果再点击右边的二级分类项目,会直接搜出该二级分类下的产品列表。

第1章到第三章:完成商城骨架的搭建。

使用命令添加如下插件

npm install fastclick --save

dependency:
bable-runtime
fastclick:无延时300毫秒插件
dev-dependency:

类型样式插件:stylus,stylus-loader

babel-polyfill

第4章:

4.1 Jsonp解决跨域问题

4.2 解决vue2.9.2版本没有dev-server.js问题

第5章 歌手列表页

5.4 Listview基于Scroll组件实现,在此组件基础上增加data属性并由外部传入,然后在Listview组件中加入v-for循环把Title和列表渲染出来即可。在Singer.vue里直接导入Listview组件并传入data数据即可;其次列表图片的懒加载,直接在Listview组件的img更改为v-lazy即可解决图片懒加载。

5.5 右侧快速入口。1、计算属性定义shortcutList得到title首字母为元素的数组。2、添加事件,首先点击事件,

 <div class="list-shortcut" @touchstart.stop.prevent="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove"
         @touchend.stop>

在Scroll里增加:ScrollTo和ScrollToElement方法。

    scrollTo() {
      this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
    },
    scrollToElement() {
      this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
    }

在Listview

onShortcutTouchStart(e) {
      let anchorIndex = getData(e.target, 'index')
      let firstTouch = e.touches[0]
      this.touch.y1 = firstTouch.pageY
      this.touch.anchorIndex = anchorIndex

      this._scrollTo(anchorIndex)
    },

3、滑动支持:

onShortcutTouchMove(e) {
      let firstTouch = e.touches[0]
      this.touch.y2 = firstTouch.pageY
      let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0
      let anchorIndex = parseInt(this.touch.anchorIndex) + delta

      this._scrollTo(anchorIndex)
    },

4. 高亮显示字母

在Scroll增加一个Props来控制是否要监听滚动事件:

listenScroll: {
  type: Boolean,
  default: false
 },

在_intiScroll方法里增加一段代码来得到列表滚动到的位置

if (this.listenScroll) {
   let me = this
   this.scroll.on('scroll', (pos) => {
   me.$emit('scroll', pos)
   })
}

这样就可以在外面监听得到scroll事件.

<scroll @scroll="scroll"
        :listen-scroll="listenScroll"
        :probe-type="probeType"
        :data="data"
        class="listview"
        ref="listview">

添加一个scroll方法

scroll(pos) {
  this.scrollY = pos.y
},

增加一个高度计算方法

先增加一个Watch

watch: {
  data() {
    setTimeout(() => {
      this._calculateHeight()
    }, 20)
  },

方法

_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() {
    setTimeout(() => {
      this._calculateHeight()
    }, 20)
  },
  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]
      if (-newY >= height1 && -newY < height2) {
        this.currentIndex = i
        this.diff = height2 + newY
        return
      }
    }
    // 当滚动到底部,且-newY大于最后一个元素的上限
    this.currentIndex = listHeight.length - 2
  },

修改Prop-type=3

让快速接口能够知道ScrollY

_scrollTo(index) {
  if (!index && index !== 0) {
    return
  }
  if (index < 0) {
    index = 0
  } else if (index > this.listHeight.length - 2) {
    index = this.listHeight.length - 2
  }
  this.scrollY = -this.listHeight[index]
  this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0)
}

Song-list

第6章

6.1 设计稿部分讲解

6.2 子路由 新建singer-detail子组件,并在router/index.js里增加路由

{
  path: '/singer',
  name: 'Singer',
  component: Singer,
  children: [
    {
      path: ':id',
      component: SingerDetail
    }
  ]
},

并在Singer组件增加route-view进行挂接

<template>
  <div class="singer" ref="singer">
    <list-view :data="singers" ref="list"></list-view>
    <router-view></router-view>
  </div>
</template>

因为是基于list-view实现点击列表项跳到歌手详情页面,所以,需要在list-view组件增加点击事件

<ul>
  <li v-for="(group,index) in data" :key='index' class="list-group" ref="listGroup">
    <h2 class="list-group-title">{{group.title}}</h2>
    <uL>
      <li @click="selectItem(item)" v-for="(item,index) in group.items" :key='index' class="list-group-item">
        <img class="avatar" v-lazy="item.avatar">
        <span class="name">{{item.name}}</span>
      </li>
    </uL>
  </li>
</ul>
methods: {
  selectItem(item) {
    this.$emit('select', item)
  },

只是派发被点击事件,所以要做Singer组件中添加监听事件并处理

<template>
  <div class="singer" ref="singer">
    <list-view @select="selectSinger" :data="singers" ref="list"></list-view>
    <router-view></router-view>
  </div>
</template>

实现selectSinger

methods: {
  handlePlaylist(playlist) {
    const bottom = playlist.length > 0 ? '60px' : ''
    this.$refs.singer.style.bottom = bottom
    this.$refs.list.refresh()
  },
  selectSinger(singer) {
    this.$router.push({
      path: `/singer/${singer.id}`
    })
  },

从而实现了跳转。

实现转场动画

实现Vuex状态管理,建立store目录创建一整套标准文件,

import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import state from './state'
import mutations from './mutations'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
  actions,
  getters,
  state,
  mutations,
  strict: debug
})

在main.js导入store,使之生效

import 'babel-polyfill'

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueLazyload from 'vue-lazyload'
import fastclick from 'fastclick'
import 'common/stylus/index.styl'

Vue.config.productionTip = false
fastclick.attach(document.body)

Vue.use(VueLazyload, {
  loading: require('common/image/default.png')
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  render: h => h(App),
  store,
  router
})

在Singer组件里设置刚才定义的singer属性

import {mapMutations} from 'vuex'
...mapMutations({
  setSinger: 'SET_SINGER'
})
selectSinger(singer) {
  this.$router.push({
    path: `/singer/${singer.id}`
  })
  this.setSinger(singer)
},

在singer-detail组件里去取

<script>
import {mapGetters} from 'vuex'

export default {
  name: 'singer-detail',
  computed: {
    ...mapGetters([
      'singer'
    ])
  }
}
</script>

6.6 数据处理和封装

首先在common下建song.js

export default class Song {
  constructor({id, mid, singer, name, album, duration, image, url}) {
    this.id = id
    this.mid = mid
    this.singer = singer
    this.name = name
    this.album = album
    this.duration = duration
    this.image = image
    this.url = url
  }
}

api/singer.js增加

export function getSingerDetail(singerId) {
  const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'

  const data = Object.assign({}, commonParams, {
    hostUin: 0,
    needNewCode: 0,
    platform: 'yqq',
    order: 'listen',
    begin: 0,
    num: 80,
    songstatus: 1,
    singermid: singerId
  })

  return jsonp(url, data, options)
}

这一节讲课中缺失了getDetail部分,把这部分放在一起singer-detail组件代码

<template>
 <transition name="slide">
  <div class="singer-detail"></div>
 </transition>
</template>

<script>
import {mapGetters} from 'vuex'
import {getSingerDetail} from 'api/singer'
import {ERR_OK} from 'api/config'
import {createSong} from 'common/js/Song'

export default {
  name: 'singer-detail',
  data() {
    return {
      songs: []
    }
  },
  computed: {
    ...mapGetters([
      'singer'
    ])
  },
  created() {
    this._getDetail()
    console.log(this.singer)
  },
  methods: {
    _getDetail() {
      if (!this.singer.id) {
        this.$router.push('/singer')
        return
      }
      getSingerDetail(this.singer.id).then((res) => {
        if (res.code === ERR_OK) {
          this.songs = this._normalizeSongs(res.data.list)
          console.log(this.songs)
        }
      })
    },
    _normalizeSongs(list) {
      let ret = []
      list.forEach((item) => {
        let {musicData} = item
        if (musicData.songid && musicData.albummid) {
          ret.push(createSong(musicData))
        }
      })
      return ret
    }
  }
}
</script>

<style scoped lang="stylus" rel="stylesheet/stylus">
@import "~common/stylus/variable"
.singer-detail
  position: fixed
  z-index : 100
  top: 0
  left: 0
  right: 0
  bottom : 0
  background : $color-background
.slide-enter-active,.slide-leave-active
  transition : all 0.3s
.slide-enter,.slide-leave-to
  transform : translate3d(100%,0,0)
</style>

至此歌手详情页面的路由配置、状态管理、数据定义和处理、数据抓取就完成了,剩下的就是把这些数据显示到界面上。

6.8 歌手详情组件,采取定义一个music-list组件,挂接到singer-detail组件上

SingerDetail

   |-- music-list

         |-- Song-list


<template>
 <div class="music-list">
   <div class="back">
     <i class="icon-back"></i>
   </div>
   <h1 class="title" v-html="title"></h1>
   <div class="bg-image" :style="bgStyle">
     <div class="filter"></div>
   </div>
   <scroll :data="songs" class="list">
     <div class="song-list-wrapper">
     <song-list :songs="songs"></song-list>
     </div>
   </scroll>
 </div>
</template>

这样挂接以后,界面song-list会把整个界面占满了

mounted() {
  this.$refs.list.$el.style.top = `${this.$refs.bgImage.clientHeight}px`
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值