网易云音乐移动端项目实战(分解中)
一、底部全局播放控件制作
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)
});
});
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'
export default {
setup() {
const router = useRouter();
const route = useRoute();
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)
});
});
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>
<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;
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
};
},
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;
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,
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(${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() {
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) {
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}}
</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';
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) {
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>
</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: {
currentTime: function(newValue) {
}
}
};
</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;
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();
}
},
changeFn: function() {
this.showpage = !this.showpage;
}
},
data() {
return {
isPause: true,
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>
`
` 固定了一下歌词高度 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" 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: {
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;
}else if(num>this.playlist.length){
num = 0;
}
this.$store.commit('setPlayindex',num)
}
},
updated() {
console.log(this.$store.getters.lyricContentFn);
},
mounted() {
},
watch: {
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>
|