网易云音乐移动端项目实战(分解中)

网易云音乐移动端项目实战(分解中)

一、底部全局播放控件制作

play-control.vue

<template>
  <div class="playcontrolor">
    <div class="playcontrol">
      <div class="left">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content">
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg class="icon play" aria-hidden="true">
            <use xlink:href="#icon-bofangqi-bofangshu"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  mounted() {
    console.log(this.playlist[this.playlistindex].al.picUrl);
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        .title {
          font-size: 0.27rem;
        }
        .toggle{
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      margin-right: 0.15rem;
      .icon {
        font-size: 0.75rem;
        color: rgba(121, 118, 118, 0.664);
      }
      .play {
        margin-right: 0.15rem;
      }
      .menu{
           font-size: 0.7rem;
      }
    }
  }
}
</style>

listview.vue

setup中this是undefined所以不能使用vue2中的方式调用store中是数据 可以直接引入store文件进行操作

    onMounted(() => {
      let id = router.currentRoute._value.query.id;
      getMusicContent(id).then(res => {
      state.playlist = res.data.playlist;
      console.log(res)
     store.commit('changeplaylist',res.data.playlist.tracks)
      });
      // const ins = getCurrentInstance();setup中使用data中的变量
    });

listview.vue完整代码

<template>
  <div class="listview">
    <listviewTop :playlist="state.playlist"/>
    <playlist :playlist="state.playlist"/>
  </div>
</template>
<script>
import { getMusicContent } from "@/api/index.js";
import { reactive, onMounted, onUpdated } from "vue";
import { useRouter, useRoute } from "vue-router";
import listviewTop from "@/components/listviewTop.vue";
import playlist from "@/components/playlist.vue";
import store from '@/store/index.js'
//reactive响应式
export default {
  setup() {
    const router = useRouter();
    const route = useRoute();
    //state是响应式对象,所以传它
    let state = reactive({ list: [], playlist: { creator: {}, tracks: [] } });
    onMounted(() => {
      let id = router.currentRoute._value.query.id;
      getMusicContent(id).then(res => {
      state.playlist = res.data.playlist;
      console.log(res)
      store.commit('changeplaylist',res.data.playlist.tracks)
      });
      // const ins = getCurrentInstance();setup中使用data中的变量
    });
    return {
      state
    };
  },
  components: {
    listviewTop,
    playlist
  }
};
</script>
<style lang="less" scoped>
.listview {
  display: flex;
  flex-direction: column;
}
</style>

vue里ref ($refs)用法
1、ref 加在普通的元素上,用this.ref.name 获取到的是dom元素
2、ref 加在子组件上,用this.ref.name 获取到的是组件实例,可以使用组件的所有方法。
3、如何利用 v-for 和 ref 获取一组数组或者dom 节点

注意:

1、ref 需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。2、如果ref 是循环出来的,有多个重名,那么ref的值会是一个数组 ,此时要拿到单个的ref 只需要循环就可以了

css文字溢出隐藏为三个点…

(1)单行

white-space: nowrap;//不换行
text-overflow: ellipsis;//将文本溢出显示为(…)
overflow: hidden;//溢出隐藏。

(2)多行

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
二、音乐播放与暂停

playlist.vue

<template>
  <div class="playlist">
    <div class="playlist-Top">
      <div class="left">
        <svg class="icon search" aria-hidden="true">
          <use xlink:href="#icon-bofang"></use>
        </svg>
        <div class="com1">
          <div class="com2">
            <div class="title">播放全部</div>
            <div class="num">(共{{playlist.tracks.length}}首)</div>
          </div>
        </div>
      </div>
      <div class="btn">+收藏({{playlist.subscribedCount}})</div>
    </div>
    <div class="list">
      <div class="listitem" v-for="(item,i) in playlist.tracks" :key="i">
        <div class="playCount">{{i+1}}</div>
        <div class="playcontent">
          <div class="h4">{{item.name}}</div>
          <div class="author">
            <span class="tag" v-for="(item,i) in playlist.tags" :key="i">{{item}}</span>
            <div class="discription">{{item.al.name}}</div>
          </div>
        </div>
        <div class="playicon">
          <svg class="icon play" aria-hidden="true">
            <use xlink:href="#icon-bofang2"></use>
          </svg>
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-diandian"></use>
          </svg>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: ["playlist"]
};
</script>
<style lang="less" scoped>
.playlist {
  border-top-left-radius: 0.3rem;
  border-top-right-radius: 0.3rem;
  background-color: #fff;
  width: 7.5rem;
  .playlist-Top {
    position: relative;
    display: flex;
    height: 1.2rem;
    align-items: center;
    width: 7.5rem;
    justify-content: space-between;
    .left {
      width: 6.7rem;
      flex: 1;
      display: flex;
      font-size: 0.4rem;
      padding-left: 0.2rem;
      .icon {
        width: 0.5rem;
        height: 0.5rem;
        font-size: 0.5rem;
      }
      .com1 {
        width: 5.5rem;
        margin-left: 0.3rem;
        display: flex;
        font-size: 0.34rem;
        font-family: "微软雅黑";
        color: #333;
        .com2 {
          display: flex;
          align-items: center;
          .num {
            line-height: 0.3rem;
            font-size: 0.3rem;
            color: rgba(187, 185, 185, 0.664);
          }
        }
      }
    }
    .btn {
      position: absolute;
      right: 0.15rem;
      font-size: 0.27rem;
      color: #fff;
      height: 0.85rem;
      line-height: 0.85rem;
      text-align: center;
      border-radius: 0.4rem;
      width: 2.4rem;
      background-color: #ff4935;
    }
  }
  .list {
    position: relative;
    width: 7.5rem;
    height: 1.2rem;
    .listitem {
      .playCount {
        height: 1.2rem;
        width: 1rem;
        text-align: center;
        line-height: 1.2rem;
        color: rgb(165, 164, 164);
        font-size: 0.36rem;
      }
      background-color: #fff;
      display: flex;
      position: relative;
      .playcontent {
        .h4 {
          padding-top: 0.1rem;
          display: flex;
          align-items: center;
          height: 0.85rem;
          font-size: 0.3rem;
        }
        .author {
          bottom: 0.1rem;
          position: absolute;
          height: 0.35rem;
          display: flex;
          align-items: center;
          span {
            width: 2.8em;
            text-align: center;
            height: 0.25rem;
            color: rgb(250, 43, 43);
            border-radius: 3px;
            font-size: 0.16rem;
            line-height: 0.2rem;
            border: 0.5px solid #ee8888;
            background-color: #ffd0c5a4;
            margin-right: 0.1rem;
            overflow: hidden;
          }
          .discription {
            color: #c2bdbd;
            height: 0.3rem;
            line-height: 0.3rem;
            font-size: 0.25rem;
            width: 3rem;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
        }
      }
      .playicon {
        // z-index: -1;
        // position: fixed;
        position: absolute;
        right: 0.25rem;
        height: 1.2rem;
        line-height: 1.2rem;
        text-align: center;
        margin-top: 0.1rem;
        .icon {
          font-size: 0.5rem;
        }
        .play {
          margin-right: 0.2rem;
        }
      }
    }
  }
}
</style>

1.ref使用
2.audio方法play() 开始播放音频pause()暂停当前播放的音频
3.muted 属性设置或返回音频是否应该被静音(关闭声音)。

 <audio ref="audio" :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
></audio>

play-controlor

<template>
  <div class="playcontrolor">
    <div class="playcontrol">
      <div class="left">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content">
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-bofang1"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
    ></audio>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  mounted() {
    console.log(this.$refs.audio);
  },
  updated() {
    console.log(this.playlist[this.playlistindex]);
  },
  methods: {
    playEvent: function() {
      if (this.$refs.audio.paused) {
        this.$refs.audio.muted = false;
        this.$refs.audio.play();
        var targeticon = document.querySelector(".right span svg use");
        targeticon.setAttribute("xlink:href", "#icon-pauseCircle");
      } else {
        this.$refs.audio.muted = true;
        this.$refs.audio.pause();
        var targeticon = document.querySelector(".right span svg use");
        targeticon.setAttribute("xlink:href", "#icon-bofang1");
      }
    }
  },
  data() {
    return {};
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        .title {
          font-size: 0.27rem;
        }
        .toggle {
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      margin-right: 0.15rem;
      .icon {
        font-size: 0.75rem;
        color: #e6e6e6;
      }
      .play {
        margin-right: 0.15rem;
        font-size: 0.64rem;
      }
      .menu {
        font-size: 0.7rem;
      }
    }
  }
}
</style>

在这里插入图片描述

三、切换歌曲

store下的index.js
添加方法修改索引值

import { createStore } from 'vuex'

export default createStore({
  state: {
    playlist:[{
      id:138164304,
      name:"不会再爱你了3.0",
      al:{
        id:138164304,
        name:"不会再爱你了3.0",
        pic:109951166868365440,
        picUrl:"http://p4.music.126.net/ajXo6RG2aM8I5yYJuPmUrQ==/109951166868365438.jpg"
    }}],
    playlistindex:0
  },
  mutations: {
    changeplaylist:function(state,value){
      state.playlist = value;
    },
    setPlayindex:function(state,indexvalue){
      state.playlistindex = indexvalue
    }
  },
  actions: {//异步获取数据然后修改对应方法
  },
  modules: {
  }
})

调用store里面的方法,修改对应的索引值
playlist.vue

<template>
  <div class="playlist">
    <div class="playlist-Top">
      <div class="left">
        <svg class="icon search" aria-hidden="true">
          <use xlink:href="#icon-bofang"></use>
        </svg>
        <div class="com1">
          <div class="com2">
            <div class="title">播放全部</div>
            <div class="num">(共{{playlist.tracks.length}}首)</div>
          </div>
        </div>
      </div>
      <div class="btn">+收藏({{playlist.subscribedCount}})</div>
    </div>
    <div class="list">
      <div class="listitem" v-for="(item,i) in playlist.tracks" :key="i" >
        <div class="playCount">{{i+1}}</div>
        <div class="playcontent">
          <div class="h4">{{item.name}}</div>
          <div class="author">
            <span class="tag" v-for="(item,i) in playlist.tags" :key="i">{{item}}</span>
            <div class="discription">{{item.al.name}}</div>
          </div>
        </div>
        <div class="playicon"  @click="setPlayindex(i)">
          <svg class="icon play" aria-hidden="true">
            <use xlink:href="#icon-bofang2"></use>
          </svg>
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-diandian"></use>
          </svg>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import {mapMutations} from 'vuex'
export default {
  props: ["playlist"],
  methods: {
    ...mapMutations(['setPlayindex']),
    
  },
};
</script>
<style lang="less" scoped>
.playlist {
  border-top-left-radius: 0.3rem;
  border-top-right-radius: 0.3rem;
  background-color: #fff;
  width: 7.5rem;
  .playlist-Top {
    position: relative;
    display: flex;
    height: 1.2rem;
    align-items: center;
    width: 7.5rem;
    justify-content: space-between;
    .left {
      width: 6.7rem;
      flex: 1;
      display: flex;
      font-size: 0.4rem;
      padding-left: 0.2rem;
      .icon {
        width: 0.5rem;
        height: 0.5rem;
        font-size: 0.5rem;
      }
      .com1 {
        width: 5.5rem;
        margin-left: 0.3rem;
        display: flex;
        font-size: 0.34rem;
        font-family: "微软雅黑";
        color: #333;
        .com2 {
          display: flex;
          align-items: center;
          .num {
            line-height: 0.3rem;
            font-size: 0.3rem;
            color: rgba(187, 185, 185, 0.664);
          }
        }
      }
    }
    .btn {
      position: absolute;
      right: 0.15rem;
      font-size: 0.27rem;
      color: #fff;
      height: 0.85rem;
      line-height: 0.85rem;
      text-align: center;
      border-radius: 0.4rem;
      width: 2.4rem;
      background-color: #ff4935;
    }
  }
  .list {
    position: relative;
    width: 7.5rem;
    height: 1.2rem;
    .listitem {
      .playCount {
        height: 1.2rem;
        width: 1rem;
        text-align: center;
        line-height: 1.2rem;
        color: rgb(165, 164, 164);
        font-size: 0.36rem;
      }
      background-color: #fff;
      display: flex;
      position: relative;
      .playcontent {
        .h4 {
              width: 5rem;
          padding-top: 0.1rem;
          display: flex;
          align-items: center;
          height: 0.85rem;
          font-size: 0.3rem;
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
      
        }
        .author {
          bottom: 0.1rem;
          position: absolute;
          height: 0.35rem;
          display: flex;
          align-items: center;
          span {
            width: 2.8em;
            text-align: center;
            height: 0.25rem;
            color: rgb(250, 43, 43);
            border-radius: 3px;
            font-size: 0.16rem;
            line-height: 0.2rem;
            border: 0.5px solid #ee8888;
            background-color: #ffd0c5a4;
            margin-right: 0.1rem;
            overflow: hidden;
          }
          .discription {
            color: #c2bdbd;
            height: 0.3rem;
            line-height: 0.3rem;
            font-size: 0.25rem;
            width: 3rem;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
        }
      }
      .playicon {
        // z-index: -1;
        // position: fixed;
        position: absolute;
        right: 0.25rem;
        height: 1.2rem;
        line-height: 1.2rem;
        text-align: center;
        margin-top: 0.1rem;
        .icon {
          font-size: 0.5rem;
        }
        .play {
          margin-right: 0.2rem;
        }
      }
    }
  }
}
</style>

只是修改了一些样式
play-controlor.vue

<template>
  <div class="playcontrolor">
    <div class="playcontrol">
      <div class="left">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content">
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-bofang1"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
    ></audio>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  mounted() {
    console.log(this.$refs.audio);
  },
  updated() {
    console.log(this.playlist[this.playlistindex]);
  },
  methods: {
    playEvent: function() {
      if (this.$refs.audio.paused) {
        this.$refs.audio.muted = false;
        this.$refs.audio.play();
        var targeticon = document.querySelector(".right span svg use");
        targeticon.setAttribute("xlink:href", "#icon-pauseCircle");
      } else {
        this.$refs.audio.muted = true;
        this.$refs.audio.pause();
        var targeticon = document.querySelector(".right span svg use");
        targeticon.setAttribute("xlink:href", "#icon-bofang1");
      }
    }
  },
  data() {
    return {};
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        width: 4.5rem;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        .title {
          font-size: 0.27rem;
        }
        .toggle {
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      width: 1.5rem;
      display: flex;
      margin-right: 0.2rem;
      justify-content: space-between;
      .icon {
        font-size: 0.75rem;
        color: #e6e6e6;
      }
      .play {
        margin-right: 0.1rem;
        font-size: 0.64rem;
      }
      .menu {
        font-size: 0.7rem;
      }
    }
  }
}
</style>
四、播放界面实现

play-controlor.vue
修改了播放的切换方式,换成了v-if更方便一些

<template>
  <div class="playcontrolor">
    <div class="playcontrol">
      <div class="left">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content">
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-bofang1"></use>
          </svg>
          <svg v-else class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-pauseCircle"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
    <!-- <playMusic :detail="playlist[playlistindex]"/> -->
    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
    ></audio>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
import playMusic from "@/components/playMusic.vue";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  mounted() {
    console.log(this.$refs.audio);
  },
  updated() {
    console.log(this.playlist[this.playlistindex]);
  },
  methods: {
    playEvent: function() {
      this.isPause = !this.isPause; //改变成true就开始播放
      if (this.isPause == true) {
        this.$refs.audio.muted = true;
        this.$refs.audio.pause();
      } else if (this.isPause == false) {
        this.$refs.audio.muted = false;
        this.$refs.audio.play();
      }
    }
  },
  data() {
    return {
      isPause: true //一开始为falseplay图标显示
    };
  },
  components: {
    playMusic
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        width: 4.5rem;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        .title {
          font-size: 0.27rem;
        }
        .toggle {
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      width: 1.5rem;
      display: flex;
      margin-right: 0.2rem;
      justify-content: space-between;
      .icon {
        font-size: 0.75rem;
        color: #e6e6e6;
      }
      .play {
        margin-right: 0.1rem;
        font-size: 0.64rem;
      }
      .menu {
        font-size: 0.7rem;
      }
    }
  }
}
</style>

在play-controlor.vue中引入playMusics.vue
1.将当前播放控制变量以及播放方法传递给子组件
2.在left左边栏设置子组件隐藏或显示,添加事件

<template>
  <div class="playcontrolor">
    <div class="playcontrol" >
      <div class="left" @click="showpage=!showpage">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content" >
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-bofang1"></use>
          </svg>
          <svg v-else class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-pauseCircle"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
    <playMusics @back="showpage=!showpage" :isPause="isPause" :playdetail="playlist[playlistindex]" v-show="showpage" :playEvent="playEvent"></playMusics>
    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
    ></audio>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
import playMusics from "@/components/playMusics.vue";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  mounted() {
    console.log(this.$refs.audio);
  },
  updated() {
 
  },
  methods: {
    playEvent: function() {
      this.isPause = !this.isPause; //改变成true就开始播放
      if (this.isPause == true) {
        this.$refs.audio.muted = true;
        this.$refs.audio.pause();
      } else if (this.isPause == false) {
        this.$refs.audio.muted = false;
        this.$refs.audio.play();
      }
    },
  },
  data() {
    return {
      isPause: true, //一开始为falseplay图标显示
      showpage: false
    };
  },
  components: {
    playMusics
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        width: 4.5rem;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        .title {
          font-size: 0.27rem;
        }
        .toggle {
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      width: 1.5rem;
      display: flex;
      margin-right: 0.2rem;
      justify-content: space-between;
      .icon {
        font-size: 0.75rem;
        color: #e6e6e6;
      }
      .play {
        margin-right: 0.1rem;
        font-size: 0.64rem;
      }
      .menu {
        font-size: 0.7rem;
      }
    }
  }
}
</style>

playMusics.vue

1.基本布局
2.z-index属性
3.跑码灯marquee使用
4.从子组件@click="$emit('back')修改父组件数据@back="showpage=!showpage"
5.在行内样式直接引用变量:src="playdetail.al.picUrl"

//文本中引用 style="{backgroundImage=url("")}"
 :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"
<template>
  <div class="playMusic">
    <div class="bg" :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"></div>
    <div class="playTop">
      <svg class="icon" aria-hidden="true" @click="$emit('back')">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <marquee behavior="scroll" direction="left" class="scrollltext">
        <div class="h2">{{playdetail.al.name}}</div>
      </marquee>
      <svg class="icon share" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
    <div class="playContent">
      <img class="needle" src="../assets/imag/needle-ab.png" :class="{active:!isPause}">
      <img class="disc" src="../assets/imag/disc-plus.png" alt>
      <img class="playimg" :src="playdetail.al.picUrl" alt>
    </div>
    <div class="progress">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-shoucang"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiazai1"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-changge"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-pinglun"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gengduo"></use>
      </svg>
    </div>
    <div class="progress"></div>
    <div class="playfooter">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-repeat2"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-bofang-copy"></use>
      </svg>
      <svg v-else class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon trans" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xinzengdaohangliebiao"></use>
      </svg>
    </div>
  </div>
</template>
<script>
export default {
  props: ["playdetail", "isPause", "playEvent"],
  computed: {},

  data() {
    return {
      bgimg: this.$store.state.playlist[this.$store.state.playlistindex].al
        .picUrl
    };
  },
  methods: {
    changeplayFn: function() {
      // playEvent();
      // this.isPause = !this.isPause;
      console.log(
        this.$store.state.playlist[this.$store.state.playlistindex].al.picUrl
      );
    }
  },
  data() {
    return {};
  }
};
</script>
<style lang="less" scoped>
.playMusic {
  position: fixed;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: rgb(0, 0, 0);
  .bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 7.5rem;
    height: 100vh;
    background-size: auto 100%;
    background-position: center;
    filter: blur(45px);
    z-index: -1;
  }
  .playTop {
    height: 1.2rem;
    margin-top: 0.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 0.3rem;
    .h2 {
      color: #fff;
      font-size: 0.38rem;
      font-weight: 800;
    }
    marquee {
      width: 5rem;
    }
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
    .share {
      font-size: 0.6rem;
    }
  }
  .playContent {
    position: absolute;
    height: 7.5rem;
    top: 1.7rem;
    left: 0;
    .needle.active {
      width: 2.5rem;
      height: auto;
      position: absolute;
      left: 3.7rem;
      z-index: 10;
      transform-origin: 0.3rem 0;
      transform: rotate(18deg);
      transition: all 1s;
    }
    .needle {
      width: 2.5rem;
      height: auto;
      position: absolute;
      left: 3.7rem;
      z-index: 10;
      transform-origin: 0.3rem 0;
      transform: rotate(-6deg);
      transition: all 1s;
    }
    .disc {
      width: 5.5rem;
      height: auto;
      position: absolute;
      left: 1rem;
      top: 3.4rem;
    }
    .playimg {
      width: 3.35rem;
      height: 3.35rem;
      border-radius: 1.5rem;
      position: absolute;
      top: 4.5rem;
      left: 2.1rem;
    }
  }
  .progress,
  .playfooter {
    width: 7.5rem;
    padding: 0 0.3rem;
    align-items: center;
    display: flex;
    justify-content: space-evenly;
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
  }
  .progress{
    position: absolute;
    bottom: 2.5rem;
    height: 1.2rem;
  }
  .playLyric {
  }
  .playfooter {
    position: absolute;
    bottom: 0.5rem;
    height: 1.2rem;
    .play {
      font-size: 1.2rem;
    }
  }
}
</style>

import { createStore } from 'vuex'

export default createStore({
  state: {
    playlist:[{
      id:138164304,
      name:"不会再爱你了3.0",
      al:{
        id:138164304,
        name:"不会再爱你了3.0",
        pic:109951166868365440,
        picUrl:"http://p4.music.126.net/ajXo6RG2aM8I5yYJuPmUrQ==/109951166868365438.jpg"
    }}],
    playlistindex:0
  },
  mutations: {
    changeplaylist:function(state,value){
      state.playlist = value;
    },
    setPlayindex:function(state,indexvalue){
      state.playlistindex = indexvalue
    }
  },
  actions: {//异步获取数据然后修改对应方法
  },
  modules: {
  }
})

dispatch:含有异步操作,例如向后台提交数据,写法: this.$store.dispatch('action方法名',)

commit:同步操作,写法:this.$store.commit('mutations方法名',)
mutations:

1、通过提交commit改变数据

2、只是一个单纯的函数

3、不要使用异步操作,异步操作会导致变量不能追踪。也就是说,用action中的函数调用mutations中的函数,进行异步操作state中的数据

在这里插入图片描述

五、获取歌词内容解析

1.找到获取歌词的api,ajax请求数据,获取对应id值的歌词详情
2.对歌词进行正则匹配,传递到store库中

<template>
  <div class="playlist">
    <div class="playlist-Top">
      <div class="left">
        <svg class="icon search" aria-hidden="true">
          <use xlink:href="#icon-bofang"></use>
        </svg>
        <div class="com1">
          <div class="com2">
            <div class="title">播放全部</div>
            <div class="num">(共{{playlist.tracks.length}}首)</div>
          </div>
        </div>
      </div>
      <div class="btn">+收藏({{playlist.subscribedCount}})</div>
    </div>
    <div class="list">
      <div class="listitem" v-for="(item,i) in playlist.tracks" :key="i" >
        <div class="playCount">{{i+1}}</div>
        <div class="playcontent">
          <div class="h4">{{item.name}}</div>
          <div class="author">
            <span class="tag" v-for="(item,i) in playlist.tags" :key="i">{{item}}</span>
            <div class="discription">{{item.al.name}}</div>
          </div>
        </div>
        <div class="playicon"  @click="setPlayindex(i)">
          <svg class="icon play" aria-hidden="true">
            <use xlink:href="#icon-bofang2"></use>
          </svg>
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-diandian"></use>
          </svg>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import {mapMutations} from 'vuex'
export default {
  props: ["playlist"],
  methods: {
    ...mapMutations(['setPlayindex']),
    
  },
};
</script>
<style lang="less" scoped>
.playlist {
  border-top-left-radius: 0.3rem;
  border-top-right-radius: 0.3rem;
  background-color: #fff;
  width: 7.5rem;
  .playlist-Top {
    position: relative;
    display: flex;
    height: 1.2rem;
    align-items: center;
    width: 7.5rem;
    justify-content: space-between;
    .left {
      width: 6.7rem;
      flex: 1;
      display: flex;
      font-size: 0.4rem;
      padding-left: 0.2rem;
      .icon {
        width: 0.5rem;
        height: 0.5rem;
        font-size: 0.5rem;
      }
      .com1 {
        width: 5.5rem;
        margin-left: 0.3rem;
        display: flex;
        font-size: 0.34rem;
        font-family: "微软雅黑";
        color: #333;
        .com2 {
          display: flex;
          align-items: center;
          .num {
            line-height: 0.3rem;
            font-size: 0.3rem;
            color: rgba(187, 185, 185, 0.664);
          }
        }
      }
    }
    .btn {
      position: absolute;
      right: 0.15rem;
      font-size: 0.27rem;
      color: #fff;
      height: 0.85rem;
      line-height: 0.85rem;
      text-align: center;
      border-radius: 0.4rem;
      width: 2.4rem;
      background-color: #ff4935;
    }
  }
  .list {
    position: relative;
    width: 7.5rem;
    height: 1.2rem;
    .listitem {
      .playCount {
        height: 1.2rem;
        width: 1rem;
        text-align: center;
        line-height: 1.2rem;
        color: rgb(165, 164, 164);
        font-size: 0.36rem;
      }
      background-color: #fff;
      display: flex;
      position: relative;
      .playcontent {
        .h4 {
              width: 5rem;
          padding-top: 0.1rem;
          display: flex;
          align-items: center;
          height: 0.85rem;
          font-size: 0.3rem;
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
      
        }
        .author {
          bottom: 0.1rem;
          position: absolute;
          height: 0.35rem;
          display: flex;
          align-items: center;
          span {
            width: 2.8em;
            text-align: center;
            height: 0.25rem;
            color: rgb(250, 43, 43);
            border-radius: 3px;
            font-size: 0.16rem;
            line-height: 0.2rem;
            border: 0.5px solid #ee8888;
            background-color: #ffd0c5a4;
            margin-right: 0.1rem;
            overflow: hidden;
          }
          .discription {
            color: #c2bdbd;
            height: 0.3rem;
            line-height: 0.3rem;
            font-size: 0.25rem;
            width: 3rem;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
        }
      }
      .playicon {
        // z-index: -1;
        // position: fixed;
        position: absolute;
        right: 0.25rem;
        height: 1.2rem;
        line-height: 1.2rem;
        text-align: center;
        margin-top: 0.1rem;
        .icon {
          font-size: 0.5rem;
        }
        .play {
          margin-right: 0.2rem;
        }
      }
    }
  }
}
</style>

store下的index.js

import { createStore } from "vuex";

export default createStore({
  state: {
    playlist: [
      {
        id: 138164304,
        name: "不会再爱你了3.0",
        al: {
          id: 138164304,
          name: "不会再爱你了3.0",
          pic: 109951166868365440,
          picUrl:
            "http://p4.music.126.net/ajXo6RG2aM8I5yYJuPmUrQ==/109951166868365438.jpg"
        }
      }
    ],
    playlistindex: 0,
    lyric: ""
  },
  getters: {
    lyricContentFn: function(state) {
      //split 把一个字符串分割成字符串数组
      //slice 方法选取基于索引的元素的子集
        let arr = state.lyric.split(/\n/igs).map((item, i) => {
        var min = item.slice(1, 3);
        var sec = item.slice(4, 6);
        var mil = item.slice(8, 10);
        var text = item.slice(11, item.length);
        var time = parseInt(mil) + parseInt(sec)*1000 + parseInt(min)*1000*60;
        return {
          time,
          min,
          mil,
          sec,
          lyric:text,
          item
        };
      });
      console.log(arr);
      return arr;
     
    }
  
  },
  mutations: {
    changeplaylist: function(state, value) {
      state.playlist = value;
    },
    setPlayindex: function(state, indexvalue) {
      state.playlistindex = indexvalue;
    },
    setLyric(state, value) {
      state.lyric = value;
    }
  },
  actions: {
    //异步获取数据然后修改对应方法
    async reLyric(content, playload) {
      content.commit("setLyric", playload);
    }
  },
  modules: {}
});

playMusics.vue

<template>
  <div class="playMusic">
    <div class="bg" :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"></div>
    <div class="playTop">
      <svg class="icon" aria-hidden="true" @click="$emit('back')">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <div class="title">
        {{playdetail.al.name}}
        <!-- <marquee direction="left" behavior="scroll">{{playdetail.al.name}}</marquee> -->
      </div>

      <svg class="icon share" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
    <div class="playContent" v-if="isLyric">
      <img class="needle" src="../assets/imag/needle-ab.png" :class="{active:!isPause}">
      <img class="disc" src="../assets/imag/disc-plus.png" alt>
      <img class="playimg" :src="playdetail.al.picUrl" alt>
    </div>
    <div class="playLyric" v-else>
      <p v-for="(item,i) in this.$store.getters.lyricContentFn" :key="i">{{item.lyric}}</p>
    </div>
    <div class="progress">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-shoucang"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiazai1"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-changge"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-pinglun"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gengduo"></use>
      </svg>
    </div>

    <div class="playfooter">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-repeat2"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-bofang-copy"></use>
      </svg>
      <svg v-else class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon trans" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xinzengdaohangliebiao"></use>
      </svg>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  props: ["playdetail", "isPause", "playEvent"],
  computed: {
    ...mapState(["playlist", "playlistindex"])
  },
  data() {
    return {
      isLyric: false
    };
  },
  methods: {},
  updated() {},
  mounted() {}
};
</script>
<style lang="less" scoped>
.playMusic {
  position: fixed;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: rgb(0, 0, 0);
  .bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 7.5rem;
    height: 100vh;
    background-size: auto 100%;
    background-position: center;
    filter: blur(45px);
    z-index: -1;
  }
  .playTop {
    height: 1.2rem;
    margin-top: 0.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 0.3rem;
    .title {
      text-align: center;
      color: #fff;
      font-size: 0.38rem;
      font-weight: 800;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 5rem;
      height: 1.2rem;
      marquee {
        width: 4rem;
      }
    }

    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
    .share {
      font-size: 0.6rem;
    }
  }
  .playContent {
    position: absolute;
    height: 7.5rem;
    top: 1.7rem;
    left: 0;
    .needle.active {
      width: 2.5rem;
      height: auto;
      position: absolute;
      left: 3.7rem;
      z-index: 10;
      transform-origin: 0.3rem 0;
      transform: rotate(18deg);
      transition: all 1s;
    }

    .needle {
      width: 2.5rem;
      height: auto;
      position: absolute;
      left: 3.7rem;
      z-index: 10;
      transform-origin: 0.3rem 0;
      transform: rotate(-6deg);
      transition: all 1s;
    }
    .disc {
      width: 5.5rem;
      height: auto;
      position: absolute;
      left: 1rem;
      top: 3.4rem;
    }
    .playimg {
      width: 3.35rem;
      height: 3.35rem;
      border-radius: 1.5rem;
      position: absolute;
      top: 4.5rem;
      left: 2.1rem;
    }
  }
    .playLyric {
      position: absolute;
      height: 9.5rem;
      top: 2rem;
      left: 0;
      width: 7.5rem;
      padding: 0 0.3rem;
      overflow: scroll;
      p {
        font-size: 0.32rem;
        line-height: 0.7rem;
        color: #fff;
        text-align: center;
      }
    }
  .progress,
  .playfooter {
    width: 7.5rem;
    padding: 0 0.3rem;
    align-items: center;
    display: flex;
    justify-content: space-evenly;
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
  }
  .progress {
    position: absolute;
    bottom: 2.5rem;
    height: 1.2rem;
  }

  .playfooter {
    position: absolute;
    bottom: 0.5rem;
    height: 1.2rem;
    .play {
      font-size: 1.2rem;
    }
  }
}
</style>

api下的index.js

import axios from 'axios';
//获取轮播图API
/*
0: pc
1: android
2: iphone
3: ipad
*/
let localhostUrl = 'http://localhost:3000'
export async function ff(type = 2) {
  return await axios.get(`${localhostUrl}/banner?type=${type}`);
  }
//获取推荐歌单默认十条数据
export  function getMusicList(limit = 10){
  return  axios.get(`${localhostUrl}/personalized?limit=${limit}`)
}
//获取歌单的详情
export async function getMusicContent(id){
  return await axios.get(`${localhostUrl}/playlist/detail?id=${id}`)
}
//获取歌词的内容
export async function getLyric(id){
  return await axios.get(`${localhostUrl}/lyric?id=${id}`)
}


网速有点慢emmmm
在这里插入图片描述

六、歌词根据时间滚动

1.定义一个函数用于获取请求歌词对应的时间信息,并且找出当前所在歌词的播放时间点,以及当前歌词的内容项
2.在dom元素中循环出得到的数组输出对应的歌词,并且有条件是否当前给定activecolor:red样式。根据当前audio属性中有currentTime的值还有获取每一句歌词的播放事件点,以及播放前一个时间点做判断语句。让其发生样式的修改。

在这里插入图片描述
2.
在这里插入图片描述

store下的index.js

import { createStore } from "vuex";
export default createStore({
  state: {
    playlist: [
      {
        id: 138164304,
        name: "不会再爱你了3.0",
        al: {
          id: 138164304,
          name: "不会再爱你了3.0",
          pic: 109951166868365440,
          picUrl:
            "http://p4.music.126.net/ajXo6RG2aM8I5yYJuPmUrQ==/109951166868365438.jpg"
        }
      }
    ],
    playlistindex: 0,
    lyric: "",
    currentTime: 0,
    id:0
  },
  getters: {
    lyricContentFn: function(state) {
      //split 把一个字符串分割成字符串数组
      //slice 方法选取基于索引的元素的子集
      let arr = state.lyric.split(/\n/gis).map((item, i, arr) => {
        var min = parseInt(item.slice(1, 3));
        var sec = parseInt(item.slice(4, 6));
        var mil = parseInt(item.slice(7, 10));
        var time = mil + sec * 1000 + min * 1000 * 60;
        return {
          time,
          min,
          mil,
          sec,
          lyric: item.slice(10, item.length).replace(/[\]]*/g, ""),
          item
        };
      });
      //实现拼接组合一个新的对象
      arr.forEach((item, i) => {
        if (i == 0) {
          item.pre = 0;
        } else {
          item.pre = arr[i - 1].time;
        }
      });
      return arr;
    } 
  },
  mutations: {
    changeplaylist: function(state, value) {
      state.playlist = value;
    },
    setPlayindex: function(state, indexvalue) {
      state.playlistindex = indexvalue;
    },
    setLyric(state, value) {
      state.lyric = value;
    },
    setCurrentTime(state, value) {
      state.currentTime = value;
    }
  },
  actions: {
    //异步获取数据然后修改对应方法
    reLyric(content, playload) {
      content.commit("setLyric", playload);
    }
  },
  modules: {
    
  }
});

playMusics.vue

<template>
  <div class="playMusic">
    <div class="bg" :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"></div>
    <div class="playTop">
      <svg class="icon" aria-hidden="true" @click="$emit('back')">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <div class="title">
        <p>{{playdetail.al.name}}</p>
        <!-- <marquee direction="left"  behavior="scroll" width="20">{{playdetail.al.name}}</marquee> -->
      </div>

      <svg class="icon share" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
    <div @click="lyricFn()" class="contentpage">
      <div class="playContent" v-if="!isLyric">
        <img class="needle" src="../assets/imag/needle-ab.png" :class="{active:!isPause}">
        <img class="disc" src="../assets/imag/disc-plus.png" alt>
        <img class="playimg" :src="playdetail.al.picUrl" alt>
      </div>
      <div class="playLyric" v-else ref="playLyrics">
        <p
          :class="{active:(currentTime*1000>=item.pre && currentTime*1000<item.time)}"
          v-for="(item,i) in this.$store.getters.lyricContentFn"
          :key="i"
        >{{item.lyric}}</p>
      </div>
    </div>
    <div class="progress">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-shoucang"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiazai1"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-changge"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-pinglun"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gengduo"></use>
      </svg>
    </div>
    <div class="playfooter">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-repeat2"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-bofang-copy"></use>
      </svg>
      <svg v-else class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon trans" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xinzengdaohangliebiao"></use>
      </svg>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  props: ["playdetail", "isPause", "playEvent"],
  computed: {
    ...mapState(["playlist", "playlistindex", "currentTime"])
  },
  data() {
    return {
      isLyric: false
    };
  },
  methods: {
    lyricFn: function() {
      this.isLyric = !this.isLyric;
    }
  },
  updated() {
    console.log(this.$store.getters.lyricContentFn);
  },
  mounted() {},
  watch: {
    //监听DOM元素
    currentTime: function(newValue) {
      // let p = document.querySelector('p.active');
      // let offsetTop = p.offsetTop;
      // this.$refs.playLyrics.scrollTop = offsetTop;
      // let offsetTop = p.offsetTop;
      // let h = this.$refs.playLyric.offsetHeight;
      // if(offsetTop>h){
      //   this.$refs.playLyric.scrollTop = this.$refs.playLyric.scrollTop + (offsetTop-h/2)
      // }
    }
  }
};
</script>
<style lang="less" scoped>
.playMusic {
  position: fixed;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: rgb(0, 0, 0);
  .bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 7.5rem;
    height: 100vh;
    background-size: auto 100%;
    background-position: center;
    filter: blur(45px);
    z-index: -1;
  }
  .playTop {
    height: 1.2rem;
    margin-top: 0.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 0.3rem;
    .title {
      text-align: center;
      color: #fff;
      font-size: 0.38rem;
      font-weight: 800;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 5rem;
      line-height: 1.2rem;
      height: 1.2rem;
      position: relative;
      overflow: hidden;
      p {
        overflow: hidden;
        position: absolute;
        top: 0;
        left: 0;
          white-space: nowrap;
        animation: 7s wordsLoop linear infinite;
        @keyframes wordsLoop {
          0% {
            transform: translateX(2rem);
          }
          100% {
            transform: translateX(-0.1rem);
          }
        }
      }
    }
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
    .share {
      font-size: 0.6rem;
    }
  }
  .contentpage {
    height: 9.5rem;
    .playContent {
      position: absolute;
      height: 7.5rem;
      top: 1.7rem;
      left: 0;
      .needle.active {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(18deg);
        transition: all 1s;
      }

      .needle {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(-6deg);
        transition: all 1s;
      }
      .disc {
        width: 5.5rem;
        height: auto;
        position: absolute;
        left: 1rem;
        top: 3.4rem;
      }
      .playimg {
        width: 3.35rem;
        height: 3.35rem;
        border-radius: 1.5rem;
        position: absolute;
        top: 4.5rem;
        left: 2.1rem;
      }
    }
    .playLyric {
      position: absolute;
      height: 9.5rem;
      top: 2rem;
      left: 0;
      width: 7.5rem;
      padding: 0 0.3rem;
      overflow: scroll;
      p {
        color: #fff;
        font-size: 0.32rem;
        line-height: 0.7rem;
        text-align: center;
      }
      .active {
        color: red;
      }
    }
  }

  .progress,
  .playfooter {
    width: 7.5rem;
    padding: 0 0.3rem;
    align-items: center;
    display: flex;
    justify-content: space-evenly;
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
  }
  .progress {
    position: absolute;
    bottom: 2.5rem;
    height: 1.2rem;
  }

  .playfooter {
    position: absolute;
    bottom: 0.5rem;
    height: 1.2rem;
    .play {
      font-size: 1.2rem;
    }
  }
}
</style>

playcontrolor.vue

<template>
  <div class="playcontrolor">
    <div class="playcontrol">
      <div class="left" @click="changeFn()">
        <img :src="playlist[playlistindex].al.picUrl">
        <div class="content">
          <div class="title">{{playlist[playlistindex].al.name}}</div>
          <div class="toggle">滑动可以切换上下首哦</div>
        </div>
      </div>
      <div class="right">
        <span>
          <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-bofang1"></use>
          </svg>
          <svg v-else class="icon play" aria-hidden="true" @click="playEvent()">
            <use xlink:href="#icon-pauseCircle"></use>
          </svg>
        </span>
        <span>
          <svg class="icon menu" aria-hidden="true">
            <use xlink:href="#icon-gedan"></use>
          </svg>
        </span>
      </div>
    </div>
    <playMusics
      @back="showpage=!showpage"
      :isPause="isPause"
      :playdetail="playlist[playlistindex]"
      v-show="showpage"
      :playEvent="playEvent"
    ></playMusics>
    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${this.playlist[this.playlistindex].id}.mp3`"
    ></audio>
  </div>
</template>
<script>
import { getLyric } from "../api/index.js";
import { mapState, mapMutations } from "vuex";
import playMusics from "@/components/playMusics.vue";
export default {
  computed: {
    ...mapState(["playlist", "playlistindex"]),
    ...mapMutations(['setCurrentTime'])
  },
  mounted() {},
  updated() {
    getLyric(this.playlist[this.playlistindex].id).then(res => {
      this.$store.dispatch("reLyric", res.data.lrc.lyric);
    });

  },
  methods: {
    UpdataTime() {
      this.$store.state.id = setInterval(() => {
        let num = this.$refs.audio.currentTime
        this.$store.commit('setCurrentTime', num);
         this.$store.getters.lyricContentFn.forEach((item,i) => {
             console.log( this.$refs.audio.currentTime*1000)
          console.log(item.time +" " + item.pre)
        });
      }, 1000);
    },
    playEvent: function() {
      this.isPause = !this.isPause; //改变成true就开始播放
      if (this.isPause == true) {
        this.$refs.audio.muted = true;
        this.$refs.audio.pause();
        clearInterval(this.$store.state.id);
      } else if (this.isPause == false) {
        this.$refs.audio.muted = false;
        this.$refs.audio.play();
        this.UpdataTime();
        // console.log( this.$refs.audio.currentTime)
      }
    },
    changeFn: function() {
      this.showpage = !this.showpage;
    }
  },
  data() {
    return {
      isPause: true, //一开始为falseplay图标显示
      showpage: false,
      id: 0
    };
  },
  components: {
    playMusics
  }
};
</script>
<style lang="less" scoped>
.playcontrolor {
  z-index: 10;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: #fff;
  .playcontrol {
    border-top: 1px solid #ccc;
    width: 7.5rem;
    height: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .left {
      display: flex;
      margin-left: 0.1rem;
      align-items: center;
      img {
        border-radius: 0.4rem;
        width: 0.8rem;
        height: 0.8rem;
      }
      .content {
        justify-content: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-left: 0.1rem;
        height: 1.2rem;
        text-align: center;
        width: 4.5rem;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        .title {
          font-size: 0.27rem;
        }
        .toggle {
          margin-top: 3px;
          color: rgb(161, 160, 160);
          font-size: 0.23rem;
        }
      }
    }
    .right {
      width: 1.5rem;
      display: flex;
      margin-right: 0.2rem;
      justify-content: space-between;
      .icon {
        font-size: 0.75rem;
        color: #e6e6e6;
      }
      .play {
        margin-right: 0.1rem;
        font-size: 0.64rem;
      }
      .menu {
        font-size: 0.7rem;
      }
    }
  }
}
</style>

在这里插入图片描述`

//感觉歌词的时间出现的值和当前获取aduio获当前时间不是对应的值,给他对应成下一句歌词时间还是不行的,我也很费解--

`
在这里插入图片描述固定了一下歌词高度
playMusics.vue

<template>
  <div class="playMusic">
    <div class="bg" :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"></div>
    <div class="playTop">
      <svg class="icon" aria-hidden="true" @click="$emit('back')">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <div class="title">
        <p>{{playdetail.al.name}}</p>
        <!-- <marquee direction="left"  behavior="scroll" width="20">{{playdetail.al.name}}</marquee> -->
      </div>

      <svg class="icon share" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
    <div @click="lyricFn()" class="contentpage">
      <div class="playContent" v-if="!isLyric">
        <img class="needle" src="../assets/imag/needle-ab.png" :class="{active:!isPause}">
        <img class="disc" src="../assets/imag/disc-plus.png" alt>
        <img class="playimg" :src="playdetail.al.picUrl" alt>
      </div>
      <div class="playLyric" v-else ref="playLyrics">
        <p
          :class="{active:(currentTime*1000>=item.pre && currentTime*1000<item.time)}"
          v-for="(item,i) in this.$store.getters.lyricContentFn"
          :key="i"
        >{{item.lyric}}</p>
      </div>
    </div>
    <div class="progress">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-shoucang"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiazai1"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-changge"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-pinglun"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gengduo"></use>
      </svg>
    </div>
    <div class="playfooter">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-repeat2"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-bofang-copy"></use>
      </svg>
      <svg v-else class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon trans" aria-hidden="true">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xinzengdaohangliebiao"></use>
      </svg>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  props: ["playdetail", "isPause", "playEvent"],
  computed: {
    ...mapState(["playlist", "playlistindex", "currentTime"])
  },
  data() {
    return {
      isLyric: false
    };
  },
  methods: {
    lyricFn: function() {
      this.isLyric = !this.isLyric;
    }
  },
  updated() {
    console.log(this.$store.getters.lyricContentFn);
  },
  mounted() {},
  watch: {
    //监听DOM元素
    currentTime: function(newValue) {
      let p = document.querySelector('p.active');
      this.$refs.playLyrics.scrollTop = p.offsetTop;
    }
  }
};
</script>
<style lang="less" scoped>
.playMusic {
  position: fixed;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: rgb(0, 0, 0);
  .bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 7.5rem;
    height: 100vh;
    background-size: auto 100%;
    background-position: center;
    filter: blur(45px);
    z-index: -1;
  }
  .playTop {
    height: 1.2rem;
    margin-top: 0.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 0.3rem;
    .title {
      text-align: center;
      color: #fff;
      font-size: 0.38rem;
      font-weight: 800;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 5rem;
      line-height: 1.2rem;
      height: 1.2rem;
      position: relative;
      overflow: hidden;
      p {
        overflow: hidden;
        position: absolute;
        top: 0;
        left: 0;
          white-space: nowrap;
        animation: 7s wordsLoop linear infinite;
        @keyframes wordsLoop {
          0% {
            transform: translateX(2rem);
          }
          100% {
            transform: translateX(-0.1rem);
          }
        }
      }
    }
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
    .share {
      font-size: 0.6rem;
    }
  }
  .contentpage {
    height: 9.5rem;
    .playContent {
      position: absolute;
      height: 7.5rem;
      top: 1.7rem;
      left: 0;
      .needle.active {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(18deg);
        transition: all 1s;
      }

      .needle {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(-6deg);
        transition: all 1s;
      }
      .disc {
        width: 5.5rem;
        height: auto;
        position: absolute;
        left: 1rem;
        top: 3.4rem;
      }
      .playimg {
        width: 3.35rem;
        height: 3.35rem;
        border-radius: 1.5rem;
        position: absolute;
        top: 4.5rem;
        left: 2.1rem;
      }
    }
    .playLyric {
      position: absolute;
      height: 9.5rem;
      top: 2rem;
      left: 0;
      width: 7.5rem;
      padding: 0 0.3rem;
      overflow: scroll;
      p {
        color: #fff;
        font-size: 0.32rem;
        line-height: 0.7rem;
        text-align: center;
      }
      .active {
        color: red;
      }
    }
  }

  .progress,
  .playfooter {
    width: 7.5rem;
    padding: 0 0.3rem;
    align-items: center;
    display: flex;
    justify-content: space-evenly;
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
  }
  .progress {
    position: absolute;
    bottom: 2.5rem;
    height: 1.2rem;
  }

  .playfooter {
    position: absolute;
    bottom: 0.5rem;
    height: 1.2rem;
    .play {
      font-size: 1.2rem;
    }
  }
}
</style>

七、上一首和下一首歌曲切换实现

1.触发事件修改歌曲列表中当前播放的index索引值加一或减一

playMusics.vue

<template>
  <div class="playMusic">
    <div class="bg" :style="{backgroundImage:`url(${playdetail.al.picUrl})`}"></div>
    <div class="playTop">
      <svg class="icon" aria-hidden="true" @click="$emit('back')">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <div class="title">
        <p>{{playdetail.al.name}}</p>
      </div>
      <svg class="icon share" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
    <div @click="lyricFn()" class="contentpage">
      <div class="playContent" v-if="!isLyric">
        <img class="needle" src="../assets/imag/needle-ab.png" :class="{active:!isPause}">
        <img class="disc" src="../assets/imag/disc-plus.png" alt>
        <img class="playimg" :src="playdetail.al.picUrl" alt>
      </div>
      <div class="playLyric" v-else ref="playLyrics">
        <p
          :class="{active:(currentTime*1000>=item.pre && currentTime*1000<item.time)}"
          v-for="(item,i) in this.$store.getters.lyricContentFn"
          :key="i"
        >{{item.lyric}}</p>
      </div>
    </div>
    <div class="progress">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-shoucang"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xiazai1"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-changge"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-pinglun"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gengduo"></use>
      </svg>
    </div>
    <div class="playfooter">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-repeat2"></use>
      </svg>
      <svg class="icon rotatedicon" aria-hidden="true" @click="goPlay(-1)">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg v-if="isPause" class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-bofang-copy"></use>
      </svg>
      <svg v-else class="icon play" aria-hidden="true" @click="playEvent">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon trans" aria-hidden="true" @click="goPlay(1)">
        <use xlink:href="#icon-xiangyou3"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xinzengdaohangliebiao"></use>
      </svg>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  props: ["playdetail", "isPause", "playEvent"],
  computed: {
    ...mapState(["playlist", "playlistindex", "currentTime",'playlistindex']),
   ...mapMutations(['setPlayindex'])

 },
  data() {
    return {
      isLyric: false
    };
  },
  methods: {
    lyricFn: function() {
      this.isLyric = !this.isLyric;
    },
    goPlay:function(value){
      let num = this.playlistindex + value;
      if(num<0){
        num = this.playlist.length-1;
        // setPlayindex()
        // playlist[playlistindex]
      }else if(num>this.playlist.length){
        num = 0;
      }
      this.$store.commit('setPlayindex',num)
    }
  },
  updated() {
    console.log(this.$store.getters.lyricContentFn);
  },
  mounted() {
    
  },
  watch: {
    //监听DOM元素
    currentTime: function(newValue) {
      let p = document.querySelector('p.active');
      this.$refs.playLyrics.scrollTop = p.offsetTop;
    }
  }
};
</script>
<style lang="less" scoped>
.playMusic {
  position: fixed;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: rgb(0, 0, 0);
  .bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 7.5rem;
    height: 100vh;
    background-size: auto 100%;
    background-position: center;
    filter: blur(45px);
    z-index: -1;
  }
  .playTop {
    height: 1.2rem;
    margin-top: 0.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 0.3rem;
    .title {
      text-align: center;
      color: #fff;
      font-size: 0.38rem;
      font-weight: 800;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 5rem;
      line-height: 1.2rem;
      height: 1.2rem;
      position: relative;
      overflow: hidden;
      p {
        overflow: hidden;
        position: absolute;
        top: 0;
        left: 0;
          white-space: nowrap;
        animation: 7s wordsLoop linear infinite;
        @keyframes wordsLoop {
          0% {
            transform: translateX(2rem);
          }
          100% {
            transform: translateX(-0.1rem);
          }
        }
      }
    }
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
    .share {
      font-size: 0.6rem;
    }
  }
  .contentpage {
    height: 9.5rem;
    .playContent {
      position: absolute;
      height: 7.5rem;
      top: 1.7rem;
      left: 0;
      .needle.active {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(18deg);
        transition: all 1s;
      }

      .needle {
        width: 2.5rem;
        height: auto;
        position: absolute;
        left: 3.7rem;
        z-index: 10;
        transform-origin: 0.3rem 0;
        transform: rotate(-6deg);
        transition: all 1s;
      }
      .disc {
        width: 5.5rem;
        height: auto;
        position: absolute;
        left: 1rem;
        top: 3.4rem;
      }
      .playimg {
        width: 3.35rem;
        height: 3.35rem;
        border-radius: 1.5rem;
        position: absolute;
        top: 4.5rem;
        left: 2.1rem;
      }
    }
    .playLyric {
      position: absolute;
      height: 9.5rem;
      top: 2rem;
      left: 0;
      width: 7.5rem;
      padding: 0 0.3rem;
      overflow: scroll;
      p {
        color: #fff;
        font-size: 0.32rem;
        line-height: 0.7rem;
        text-align: center;
      }
      .active {
        color: red;
      }
    }
  }

  .progress,
  .playfooter {
    width: 7.5rem;
    padding: 0 0.3rem;
    align-items: center;
    display: flex;
    justify-content: space-evenly;
    .icon {
      color: #fff;
      font-size: 0.7rem;
    }
  }
  .progress {
    position: absolute;
    bottom: 2.5rem;
    height: 1.2rem;
  }

  .playfooter {
    position: absolute;
    bottom: 0.5rem;
    height: 1.2rem;
    .play {
      font-size: 1.2rem;
    }
    .rotatedicon{
    transform: rotate(-180deg);
    }
  }
}
</style>

在这里插入图片描述

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可可鸭~

想吃糖~我会甜

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值