前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记。

项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star。


当前歌曲播放列表 添加歌曲到队列

components->playlist目录下:创建playlist.vue -- 在play.vue中应用

一、.歌曲列表组件显示和隐藏的控制
  • data中维护一个数据

    showFlag: false
  • 使用v-show判断showFlag控制显示隐藏
    <div class="playlist" v-show="showFlag">
  • methods中分别定义show()和hide(),设置showFlag为true和false
    show() {
    this.showFlag = true
    },
    hide() {
    this.showFlag = false
    }
  • player.vue中:给列表按钮添加点击事件,并阻止事件冒泡,showPlaylist方法执行playlist中的show()控制列表的显示
    <div class="control" @click.stop.prevent="showPlaylist">
    <play-list ref="playlist">
    showPlaylist() {
    this.$refs.playlist.show()
    }
  • playlist.vue中:给列表的蒙层和关闭按钮都添加点击事件,触发hide方法,控制列表的隐藏
  • 坑:点击列表本身也会关闭列表
  • 原因:给列表蒙层添加点击事件时,实际上添加到了最外层<div class="playlist">上,点击子元素时会事件冒泡到最外层
  • 解决:给list-wrapper添加@click.stop,阻止点击事件冒泡 
    <div class="list-wrapper" @click.stop>
二、歌曲列表组件播放列表的实现
  • 通过mapGetters获取顺序播放的歌曲列表添加到列表项

    import {mapGetters} from 'vuex'
    
    computed: {
    ...mapGetters([
    'sequenceList'
    ])
    }
    <li class="item" v-for="(item, index) in sequenceList" :key="index">
    <span class="text">{{item.name}}</span>
  • 应用Scroll组件实现歌曲列表滚动

    <scroll class="list-content" :data="sequenceList" ref="listContent">
  • 在show()时让scroll重新进行计算,确保当前高度是正确的

    show() {
    this.showFlag = true
    setTimeout(() => {
    this.$refs.listContent.refresh()
    }, 20)
    }
  • 给当前播放的歌曲添加current高亮显示的样式

    <i class="current" :class="getCurrentIcon(item)"></i>

    通过mapGetters获取当前歌曲: 'currentSong'

    getCurrentIcon(item) {
    if(this.currentSong.id === item.id) {
    return 'icon-play'
    }
    return ''
    }
  • 选择列表项播放歌曲
    <li class="item" @click="selectItem(item, index)">

    通过mapGetters获得currentSong和playlist,通过mapMutations调用setCurrentIndex提交数据

    import {playMode} from '@/common/js/config'
    
    computed: {
    ...mapGetters([
    'sequenceList',
    'currentSong',
    'playlist',
    'mode'
    ])
    } selectItem(item, index){
    //如果当前是随机播放,重新计算index
    if(this.mode === playMode.random) {
    inde = this.playlist.findIndex((song) => {
    return song.id === item.id
    })
    }
    this.setCurrentIndex(index)
    this.setPlayingState(true)
    } ...mapMutations({
    setCurrentIndex: 'SET_CURRENT_INDEX',
    setPlayingState: 'SET_PLAYING_STATE'
    })
  • 歌曲列表滚动到当前播放的歌曲
  1. 需求:切换歌曲时,歌曲列表滚动到当前播放的歌曲位置;打开歌曲列表时,当前播放的歌曲始终在第一行显示
  2. 封装一个滚动到当前播放歌曲的方法
    scrollToCurrent(current) {
    //找到当前歌曲在顺序列表中的索引
    const index = this.sequenceList.findIndex((song) => {
    return current.id === song.id
    })
    this.$refs.listContent.scrollToElement(this.$refs.listItem[index], 300)
    }
  3. 切换歌曲成功时调用
    watch: {
    currentSong(newSong, oldSong) {
    if(!this.showFlag || newSong.id === oldSong.id) {
    return
    }
    this.scrollToCurrent(newSong)
    }
    }
  4. 歌曲列表显示时调用
    show() {
    this.showFlag = true
    setTimeout(() => {
    this.$refs.listContent.refresh()
    this.scrollToCurrent(this.currentSong)
    }, 20)
    }
  • 从歌曲播放列表中删掉所选歌曲

    <span class="delete" @click.stop="deleteOne(item)">
  1. actions.js中:封装deleteSong()

    export const deleteSong = function ({commit, state}, song){
    let playlist = state.playlist.slice() //副本
    let sequenceList = state.sequenceList.slice() //副本
    let currentIndex = state.currentIndex let pIndex = findIndex(playlist, song)
    playlist.splice(pIndex, 1) let sIndex = findIndex(sequenceList, song)
    sequenceList.splice(sIndex, 1) if(currentIndex > pIndex || currentIndex === playlist.length){
    currentIndex--
    }
    commit(types.SET_PLAYLIST, playlist)
    commit(types.SET_SEQUENCE_LIST, sequenceList)
    commit(types.SET_CURRENT_INDEX, currentIndex) const playingState = playlist.length > 0
    commit(types.SET_PLAYING_STATE,playingState)
    }
  2. 通过mapActions获取deleteSong方法
    ...mapActions([
    'deleteSong'
    ])
  3. methods中定义deleteOne(),调用deleteSong()
    deleteOne(item) {
    this.deleteSong(item)
    if(!this.playlist.length){
    this.hide()
    }
    }
  4. 坑:报错this.currentSong.getLyric is not a function
  5. 原因: action deleteSong修改了playlist和currentIndex,导致currentSong发生变化;在player.vue中的watch会监测到currentSong变化了,但其实这里列表中已经没有歌曲了;newSong为空的Object,此时将newSong和oldSong进行对比,都会返回undefined,所以报错
  6. 解决:在watch currentSong()中判断如果没有newSong.id,直接返回,不执行任何操作
    if(!newSong.id) {
    return
    }
  • 优化:给删除歌曲添加动画
  1. 将<ul>替换为

    <transition-group name="list" tag="ul">
  2. transition-group的关键: 内容<li class="item">必须要有 :key="index"
    .item
    height: 40px
    &.list-enter-active, &.list-leave-active
    transition: all 0.1s
    &.list-enter, &.list-leave-to
    height: 0
  • 清除歌曲播放列表
  1. 同搜索历史列表,在点击清除按钮后,需要先显示一个confirm弹窗,然后选择确认或取消
  2. 给清空按钮添加点击事件,显示弹窗:
    <span class="clear" @click="showConfirm">
    showConfirm() {
    this.$refs.confirm.show()
    }
  3. 给弹窗监听confirm事件,同时封装action,在confirm()中调用
    <confirm ref="confirm" text="是否清空播放列表" confirmBtnText="清空"
    @confirm="confirmClear"></confirm>
    confirmClear() {
    this.deleteSongList()
    this.hide()
    }
    ...mapActions([
    'deleteSong',
    'deleteSongList'
    ])

    actions.js中:

    export const deleteSongList = function ({commit}){
    //将所有值都重置为初始状态
    commit(types.SET_PLAYLIST, [])
    commit(types.SET_SEQUENCE_LIST, [])
    commit(types.SET_CURRENT_INDEX, -1)
    commit(types.SET_PLAYING_STATE, false)
    }
  4. 坑:点击取消时,歌曲播放列表也会被关闭
  5. 原因:在playlist.vue中<confirm>在<div class="playlist" @click="hide">内,点击confirm会事件冒泡,触发hide
  6. 解决:让confirm组件更加独立,阻止事件冒泡 
    <div class="confirm" v-show="showFlag" @click.stop>
三、playerMixin的抽象
  • 需求:歌曲播放列表中的播放模式切换功能与播放器中的逻辑相同,可以使用Mixin复用
  • mixin.js中:创建新的playerMixin对象

    import {mapGetters, mapMutations} from 'vuex'
    import {playMode} from '@/common/js/config'
    import {shuffle} from '@/common/js/util' export const playerMixin = {
    computed: {
    iconMode(){
    return this.mode === playMode.sequence ? 'icon-sequence' : this.mode ===
    playMode.loop ? 'icon-loop' : 'icon-random'
    },
    ...mapGetters([
    'sequenceList',
    'currentSong',
    'playlist',
    'mode'
    ])
    },
    methods: {
    changeMode(){
    const mode = (this.mode + 1) % 3
    this.setPlayMode(mode)
    let list = null
    if(mode === playMode.random){
    list = shuffle(this.sequenceList)
    }else{
    list = this.sequenceList
    }
    this.resetCurrentIndex(list)
    this.setPlayList(list)
    },
    resetCurrentIndex(list){
    let index = list.findIndex((item) => { //es6语法
    return item.id === this.currentSong.id
    })
    this.setCurrentIndex(index)
    },
    ...mapMutations({
    setPlayingState: 'SET_PLAYING_STATE',
    setCurrentIndex: 'SET_CURRENT_INDEX',
    setPlayMode: 'SET_PLAY_MODE',
    setPlayList: 'SET_PLAYLIST'
    })
    }
    }
  • player和playlist中:应用playerMixin,同时删掉共用部分

    import {playerMixin} from '@/common/js/mixin'
    mixins:[playerMixin],
  • playlist.vue中:动态绑定class,监听点击事件

    <i class="icon" :class="iconMode" @click="changeMode">
    computed: {
    modeText() {
    return this.mode === playMode.sequence ? '顺序播放' : this.mode === playMode.random ? '随机播放' : '单曲循环'
    }
    }
四、添加歌曲到列表add-song组件实现
  • components->add-song目录下:创建add-song.vue
  • 页面的显示隐藏:同playlist.vue
  • 搜索框和搜索结果:与search.vue共有一些相同的逻辑,使用mixin复用
  1. mixin.js中创建新的searchMixin对象

    export const searchMixin = {
    computed: {
    ...mapGetters([
    'searchHistory'
    ])
    },
    data() {
    return {
    query: ''
    }
    },
    methods: {
    blurInput() {
    this.$refs.searchBox.blur()
    },
    saveSearch() {
    this.saveSearchHistory(this.query)
    },
    onQueryChange(query){
    this.query = query
    },
    addQuery(query) {
    this.$refs.searchBox.setQuery(query)
    },
    ...mapActions([
    'saveSearchHistory',
    'deleteSearchHistory'
    ])
    }
    }
  2. search和add-song中:应用searchMixin,同时删掉共用部分
    import {searchMixin} from '@/common/js/mixin'
    
    mixins:[searchMixin],
  3. add-song.vue中:绑定数据,监听事件
    <search-box ref="searchBox" @query="onQueryChange" placeholder="搜索歌曲">
    <suggest :query="query" :showSinger="showSinger" @select="selectSuggest" @listScroll="blurInput">
  • 切换Tab组件
  1. base->switches目录下:创建switches.vue

    <li class="switch-item" v-for="(item, index) in switches" :key="index"
    :class="{'active': currentIndex === index}" @click="switchItem(index)">
    <span>{{item.name}}</span>
    </li>
    props: {
    switches: {
    type: Array,
    default: []
    },
    currentIndex: {
    type: Number,
    default: 0
    }
    },
    methods: {
    switchItem(index) {
    this.$emit('switch', index)
    }
    }
  2. add-song.vue中应用:
    <switches :switches="switches" :currentIndex="currentIndex" @switch="switchItem"></switches>
    currentIndex: 0,
    switches: [
    {name: '最近播放'},
    {name: '搜索历史'}
    ]
    switchItem(index) {
    this.currentIndex = index
    }
  • 最近播放列表
  1. Vuex管理数据

    ①states.js中:添加数据

    playHistory: []

    ②mutations-types.js中:定义事件类型常量

    export const SET_PLAY_HISTORY = 'SET_PLAY_HISTORY'

    ③mutations.js中:创建方法

    [types.SET_PLAY_HISTORY](state, history){
    state.playHistory = history
    }

    ④getters.js中:定义数据映射

    export const playHistory = state => state.playHistory
  2. player.vue中:当歌曲ready后通过mapActions向Vuex中写入数据
    ready() {
    this.songReady = true
    this.savePlayHistory(this.currentSong)
    }
    ...mapActions([
    'savePlayHistory'
    ])
  3. catch.js中:实现对本地缓存的操作
    //将歌曲数据保存到本地缓存
    export function savePlay(song) {
    let songs = storage.get(PLAY_KEY, [])
    insertArray(songs, song, (item) => {
    return item.id === song.id
    }, PLAY_MAX_LENGTH)
    storage.set(PLAY_KEY, songs)
    return songs
    }
    //从本地缓存中取出歌曲数据
    export function loadPlay() {
    return storage.get(PLAY_KEY, [])
    }
  4. state.js中:修复playHistory初始值为当前本地缓存中的数据
    playHistory: loadPlay()
  5. actions.js中:引入savePlay方法将数据同时存入Vuex和本地缓存
    export const savePlayHistory = function({commit}, song){
    commit(types.SET_PLAY_HISTORY, savePlay(song))
    }
  6. add-song.js中:通过mapGetters获取Vuex中的播放历史
    <div class="list-wrapper">
    <scroll class="list-scroll" v-if="currentIndex===0" :data="playHistory">
    <div class="list-inner">
    <song-list :songs="playHistory"></song-list>
    </div>
    </scroll>
    </div>
    import Scroll from '@/base/scroll/scroll'
    import {mapGetters} from 'vuex'
    import SongList from '@/base/song-list/song-list' computed: {
    ...mapGetters([
    'playHistory'
    ])
    }
  7. add-song.js中:监听select事件,通过mapActionis从播放历史中选择歌曲插入播放列表
    @select="selectSong"
    import Song from '@/common/js/song'
    
    selectSong(song, index) {
    if(index !== 0) {
    //从playHistory中获取到的song还是一个对象,需要实例化为Song类
    this.insertSong(new Song(song))
    }
    },
    ...mapActions([
    'insertSong'
    ])
  8. 搜索历史列表:复用SearchList组件以及searchMixin中的数据和方法
    <scroll ref="searchList" class="list-scroll" v-if="currentIndex===1"
    :data="searchHistory">
    <div class="list-inner">
    <search-list @delete="deleteSearchHistory" @select="addQuery"
    :searches="searchHistory"></search-list>
    </div>
    </scroll>
  9. 优化:在search-list.vue中通过transition-group给删除列表时添加动画
  10. 关键:<transition-group>中的元素一定要有key值区分元素间的不同
  11. 优化:在添加歌曲到列表页面显示时,判断当前显示列表,对应scroll重新计算,确保高度正确
    show() {
    this.showFlag = true
    setTimeout(() => {
    if(this.currentIndex === 0){
    this.$refs.songList.refresh()
    }else{
    this.$refs.searchList.refresh()
    }
    })
    }
  • 顶部提示框
  1. base->top-list目录下:创建top-list.vue

    <transition name="drop">
    <div class="top-tip" v-show="showFlag" @click.stop="hide">
    <slot></slot>
    </div>
    </transition>

    通过showFlag控制显示隐藏,同play-list.vue

  2. add-song.vue中:应用top-list,添加slot
    <top-tip ref="topTip">
    <div class="tip-title">
    <i class="icon-ok"></i>
    <span class="text">1首歌曲已经添加到播放队列</span>
    </div>
    </top-tip>

    分别在selectSuggest()和selectSong()中调用showTip()显示提示框

  3. top-list.vue中:在show()内调用定时器,设置提示框显示2s自动关闭
  4. 坑:如果快速的调用show(),会有很多定时器存在
  5. 解决:每次show()时,在调用新的timer前就先清空前面的timer
    props: {
    delay: {
    type: Number,
    default: 2000
    }
    } show() {
    this.showFlag = true
    clearTimeout(this.timer)
    this.timer = setTimeout(() => {
    this.hide()
    }, this.delay)
    }
五、歌曲列表组件scroll组件能力的扩展
  • 坑:添加歌曲到列表后,歌曲播放列表的滚动位置不对了
  • 原因:playlist中歌曲列表项的添加了transition-group动画,即添加歌曲后歌曲播放列表的高度需要一个100ms的过程;而外层<scroll :data="sequenceList">在watch到数据的变化后,20ms就重新计算了,计算的高度是不对的
  • 解决:扩展scroll组件,添加props属性refreshDelay,让外部组件可以自定义重新计算的延迟时间;扩展后再在外部组件的data中定义refreshDelay:100,最后在<scroll>中传入:refreshDelay="refreshDelay"

    refreshDelay: {
    type: Number,
    default: 20
    } watch: {
    data() { //监测data的变化
    setTimeout(() => {
    this.refresh()
    }, this.refreshDelay)
    }
    }
  • 同上:search-list组件中的列表项也添加了transition-group动画;在引用到的search组件和add-song组件中都需要传入:refreshDelay="refreshDelay";因为两个组件复用了searchMixin,所以在searchMixin中定义refreshDelay:100

注:项目来自慕课网

【音乐App】—— Vue-music 项目学习笔记:歌曲列表组件开发的更多相关文章

  1. 【音乐App】—— Vue-music 项目学习笔记:推荐页面开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 上一篇总结了项目概述.项目准备.页面骨架搭建.这一篇重点梳理推荐页面开发.项目github地址:https://github.com/66We ...

  2. 【音乐App】—— Vue-music 项目学习笔记:搜索页面开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 搜索歌手歌曲 搜索历史保存 ...

  3. 【音乐App】—— Vue-music 项目学习笔记:歌手页面开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 一.歌手页面布局与设计 需 ...

  4. 【音乐App】—— Vue-music 项目学习笔记:歌手详情页开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌曲列表 歌曲播放 一.子 ...

  5. 【音乐App】—— Vue-music 项目学习笔记:播放器内置组件开发(二)

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 播放模式切换 歌词滚动显示 ...

  6. 【音乐App】—— Vue-music 项目学习笔记:用户个人中心开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌曲列表 收藏歌曲 一.用 ...

  7. 【音乐App】—— Vue-music 项目学习笔记:项目准备

    前言: 学习慕课网Vue高级实战课程后,在实践中总结一些这个项目带给自己的收获,希望可以再次巩固关于Vue开发的知识.这一篇主要梳理:项目概况.项目准备.页面骨架搭建.项目github地址:https ...

  8. 【音乐App】—— Vue-music 项目学习笔记:歌单及排行榜开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌单及详情页 排行榜及详情 ...

  9. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

随机推荐

  1. 04 JVM是如何执行方法调用的(下)

    虚方法调用 Java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用会被编译成 invokeinterface 指令.这两种指令,均属于 Java 虚拟机中的虚 ...

  2. Linux下librtmp使用及编程实战

    最近想做rtmp的推流.直播的小项目,不想直接使用FFmpeg进行推流,FFmpeg进行推流特别简单,因为它已经将编码以及librtmp都集成好了,没啥意思.FFmpeg推流的例子,在雷神的博客里可以 ...

  3. html之表单标签

    表单标签的属性: 用于向服务器传输数据 表单能够包含input元素,比如文本字段,复选框,单选框,提交按钮等等 表单还可以包含textarea(简介之类的),select(下拉),fieldset和l ...

  4. 快速排序(Quick Sort)及优化

    原理介绍 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成 ...

  5. SRCNN(一)

    SRCNN学习(一):demo_SR.m 一.demo_SR.m 使用方法 1.Place the "SRCNN" folder into "($Caffe_Dir)/e ...

  6. vmware esxi 查看网卡、Raid卡驱动

    vmware esxi 查看网卡.Raid卡驱动 http://blog.51cto.com/adamcrab/1942763 查看网卡 [root@localhost:~] esxcfg-nics  ...

  7. ABC128F Frog Jump

    题目链接 题目大意 给定一个长为 $n$ 的数组 $s$,下标从 $0$ 开始.$ 3 \le n \le 10^5$,$-10^9 \le s_i \le 10^9$,$s_0 = s_{n - 1 ...

  8. HDU 3333 Turing Tree(离线树状数组)

    Turing Tree Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Tota ...

  9. nodeJS学习(3)--- npm 配置和安装 express4.X 遇到的问题及解决

    前言:懒得看前面两篇介绍的也可以从本节直接参考,但建议最好了解下,因为 4.X 的express 已经把命令行工具分离出来 (链接https://github.com/expressjs/genera ...

  10. HDOJ 1171 Big Event in HDU

    Big Event in HDU Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...