vue09----vuex辅助函数、Detail.vue跳转Player.vue、封装mutations中的函数名、utils工具的使用、&符号、audio标签的timeupdate事件和ended事件、点击play按钮实现播放和暂停,同时img对应旋转或暂停旋转、上一曲(prev)和下一曲(next)、props传值以对象形式对象接收、watch监听控制进度条的宽度、点击滚动条控制播放时间

### 样式作用域

    scoped

### vuex的辅助函数(Player.vue)

    mapState:将state值直接映射到计算属性
 
        原来拿到state中的songList:
            computed: {
                songList(){
                    return this.$store.state.songList;
                }
            }
        通过mapState拿到state中的songList:
            computed: {
                ...mapState(["songList"])
            }
 
    步骤:
        ①引入
            import {mapState,mapMutations,mapActions,mapGetters} from "vuex";
        ②computed中使用...mapState()拿到store中state中的属性,使用...mapGetters拿到store中getters中的方法
            computed: {
                // songList(){
                //     return this.$store.state.songList.length;
                // }
                ...mapState(["songList","fullScreen","currentIndex"]),
                // currentSong(){
                //     return this.$store.getters.currentSong
                // }
                ...mapGetters(["currentSong"])
            }

### 点击Detail.vue列表歌曲,跳转至Player.vue

    ①在store.js中,声明歌单列表和当前播放歌曲的下标
        const store=new Vuex.Store({
            state:{
                playing:false,
                fullScreen:false,
                songList:[],     // 歌单列表
                currentIndex:-1,     // 当前正在播放的歌曲
            },
            mutations:{
                setSongList(state,list){
                    state.songList=list;
                },
                setCurrentIndex(state,index){
                    state.currentIndex=index;
                }
            },
            getters:{
                currentSong(state){
                    // 根据下标获取当前播放歌曲的信息
                    return state.songList[state.currentIndex];
                }
            }
        })
    ②Detail.vue中通过goPlayer()方法中的commit()触发mutations中的方法,将当前歌手的歌单列表和当前下标分别传过去
        goPlayer(index){
            this.$store.commit("setSongList",this.list);
            this.$store.commit("setCurrentIndex",index);
        }
    ③Player.vue中,可以通过getters拿到
        {{this.$store.getters.currentSong}}
        这个取值的方法太繁琐了,可以通过辅助函数mapGetters拿到:
            computed: {
                ...mapGetters(["currentSong"])
            }
        然后就{{currentSong}}就可以了
    最好是通过actions来触发,稍作修改:
        store.js中:
            actions:{
                addSongList({commit},{index,list}){
                    commit("setSongList",list);
                    commit("setCurrentIndex",index);
                }
            },
        Detail.vue中:
            goPlayer(index){
                // this.$store.commit("setSongList",this.list);
                // this.$store.commit("setCurrentIndex",index);
                this.$store.dispatch("addSongList",{list:this.list,index:index});
            }
        注意:actions的用途就是处理异步和做多个commit的封装。

### 利用const将mutations中函数的名字做封装,避免命名重复(store.js)

    ①store文件夹下新建mutations-type.js文件:
        const SET_SONG_LIST="SET_SONG_LIST";
        const SET_CURRENT_INDEX="SET_CURRENT_INDEX";
 
        export default{
            SET_SONG_LIST,
            SET_CURRENT_INDEX
        }
    ②store.js中用常量名替代原来的函数名:
        引入:
        import type from "./mutations-type.js"
        mutations和actions中:
            mutations:{
                [type.SET_SONG_LIST](state,list){
                    state.songList=list;
                },
                [type.SET_CURRENT_INDEX](state,index){
                    state.currentIndex=index;
                }
            },
            actions:{
                addSongList({commit},{index,list}){
                    commit(type.SET_SONG_LIST,list);
                    commit(type.SET_CURRENT_INDEX,index);
                }
            },

### utils工具的使用(Player.vue)

    ①在src下新建utils/formatUrl.js:
        // 专辑
        export const albumUrl=(albumMid)=>{
            return `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg?max_age=2592000`;
        }
        // 歌曲
        export const songUrl=(songMid)=>{
            return `http://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/C400${songMid}.m4a?guid=4887996690&vkey=16121C6B6E73C564FA01F98651C808B32B6D8788E8C7A1FCCFD030B194EC90658BB78D0A988B6DBC9F0C6E1535FD194E2C459CE01510E438&uin=0&fromtag=38`;
        }
    ②引入:(Player.vue)
        import * as urlObj from "utils/formatUrl.js";
        或
        import {albumUrl,songUrl} from "utils/formatUrl.js";
    注意:
        1、export default{} 只能抛出一个对象,如果要抛出多个用 export const ...
        2、引入的时候加上 * as,或者通过解构:import {albumUrl,songUrl} from "utils/formatUrl.js";

### &符号的意思(Player.vue)

    img{
        .w(300);
        .h(300);
        border-radius: 50%;
        border: 10px solid hsla(0,0%,100%,.1);
        box-sizing: border-box;
        &.play{
            animation: rotate 20s linear infinite;
        }
        &.paused{
            animation-play-state: paused;
        }
    }
    加&表示play、paused和img是同级的,不加&表示play、paused是img的子级。

### audio标签的timeupdate事件:当前播放时间(Player.vue)

    audio绑定timeupdate事件,可以通过e.target.currentTime拿到当前播放时间
    ①定义timeupdate事件
    <audio controls @timeupdate="timeUpdate"></audio>
    ②将当前时间赋给current当前时间
        timeUpdate(e){
            console.log("时间变化",e.target.currentTime)
            this.current=e.target.currentTime;
        }

### 点击play按钮,实现播放和暂停,播放时img旋转,暂停时img暂停旋转(Player.vue)

    ①给img标签绑定类名playClass
        <img :class="playClass" v-if="currentSong" :src="albumUrl">
    ②按钮添加点击事件play
        <div class="control">
            <button @click="play">play</button>
        </div>
    ③data中声明playing为false
        data() {
            return {
                playing:false
            }
        }
    ④设置play和paused类名的样式
        img{
            .w(300);
            .h(300);
            border-radius: 50%;
            border: 10px solid hsla(0,0%,100%,.1);
            box-sizing: border-box;
            &.play{
                animation: rotate 20s linear infinite;
            }
            &.paused{
                animation-play-state: paused;
            }
        }
    ⑤computed中将playing的值赋给playClass类
        playClass(){
            return this.playing?"play":"play paused";
        }
        接下来控制playing的布尔值就可以控制img的旋转
    ⑥play()方法中判断是否处于暂停中,如果是点击播放,否则点击暂停,同时设置playing值
        play(){
            let audio=this.$refs.audio;
            if(audio.paused){
                audio.play();
                this.playing=true;
            }else{
                audio.pause();
                this.playing=false;
            }
            console.log(audio.__proto__)
        }
    注意:
        1、添加类名的时候不可以写 return this.playing?"play":"paused"; 这样会造成暂停过后,img重新开始旋转。
        2、打印audio标签的__proto__对象,可以获取audio的一系列属性和方法。

### 上一曲(prev)和下一曲(next)(Player.vue)

    ①添加prev和next按钮:
        <div class="control">
            <button @click="prev">prev</button>
            <button @click="play">play</button>
            <button @click="next">next</button>
        </div>
    ②通过commit()触发mutations中的SET_CURRENT_INDEX方法:
        next(){
            let index=this.currentIndex+1;
            this.$store.commit("SET_CURRENT_INDEX",index);
        },
        prev(){
            let index=this.currentIndex-1;
            this.$store.commit("SET_CURRENT_INDEX",index);
        }
    ③边界判断,当SET_CURRENT_INDEX方法中判断当index大于列表最后一项时,让index为0;当index小于列表第一项时,让index为列表最后一项。这就是循环播放:
        [type.SET_CURRENT_INDEX](state,index){
            if(index>state.songList.length-1){
                index=0;
            }else if(index<0){
                index=state.songList.length-1;
            }
            state.currentIndex=index;
        },

### audio标签的ended事件:播放完成触发(Player.vue)

    ①
    <audio controls @ended="ended"></audio>
    ②
    ended(){
          console.log("播放完成")
    }

### props传值(Player.vue、P-scrollBar.vue)

    props用于父传子传值。
 
    传递:当子组件在父组件中当做标签传值使用的时候,给当前子组件绑定一个自定义属性,值为需要传递的数据。
        <one :val="msg"></one>
 
    接收:在子组件内部通过props属性来进行接收。props接收的方式有数组和对象两种:    
        数组接收:
          props:["msg"]
        对象接收:
          props:{
                    msg:{
                        type:Number,
                        default:0,
                        required:true
                    }
                }
            type:限制外部数据的类型
            default:默认值,当父组件没有给子组件传值时用默认值
            required:布尔值,当前属性是不是必传的值,如果值为true,不传msg属性时会报错
 
    传递:(Player.vue)
        <PScrollBar :current="current" :duration="duration"></PScrollBar>
    接收:(P-scrollBar.vue)
        props:{
            "current":{type:Number,default:0},
            "duration":{type:Number,default:0},
        }

### watch监听控制进度条的宽度(P-scrollBar.vue)

    watch: {
        current(newValue){
            let percentage=(newValue/this.duration)*100;
            this.$refs.container.style.width=`${percentage}%`;
        }
    }
    注意:watch中的方法是data中的属性,watch依赖于data,当data中的属性发生改变的时候,触发watch中函数执行。current()是data中的属性,这里它是由props传递过来的,当current()执行时,进度条宽度被不断地重新定义。

### 点击滚动条控制播放时间(P-scrollBar.vue)

    ①给wrapper盒子绑定点击事件clickDarg
        <div class="wrapper" @click="clickDarg" ref="wrapper">
            <div class="container" ref="container">
 
            </div>
        </div>
    ②将比例赋给container盒子,并将这个百分比传给父组件Player.vue
        clickDarg(e){
            let percentage=(e.offsetX/this.$refs.wrapper.clientWidth)*100;
            this.$refs.container.style.width=`${percentage}%`;
            // 派发jump事件给Player.vue
            this.$emit("jump",percentage);
        }
    ③给PScrollBar组件绑定jump事件
        <PScrollBar :current="current" :duration="duration" @jump="jump"></PScrollBar>
    ④audio的currentTime属性可读也可写
        jump(percentage){
            this.$refs.audio.currentTime=this.duration*percentage/100;
        }