其他页面可以看我页面专栏 Vue3实战项目-网易云APP 。
大家觉得有些地方可以写的更好写法可以给我留言私信,我会去修改
如果文章对你有帮助请点一个赞或收藏,谢谢
接下来我们完成播放的组件
首先,我们看手机上的网易云可以知道它是一个全局的组件,所以我们就在App.vue中注册一个。
然后我们在component中创建playController.vue组件,把基础的结构填好,还是分为左右两边。
<template>
<!-- 播放 -->
<div class="playController">
<!-- 图片和名字部分 -->
<div class="left">
<img src="" alt="">
<div class="content">
<div class="title"></div>
</div>
</div>
<!-- 播放按钮和列表按钮 -->
<div class="right">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-bofang"></use>
</svg>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-24gf-playlistHeart"></use>
</svg>
</div>
</div>
</template>
左边没有数据,所以就没显示,然后我们来弄数据,首先它是个全局组件,在每个页面都有显示,所以数据是全局的,我们在store下传递值(这里是利用的vuex)如果最开始创建项目时没选择安装需要安装一下
然后我们在 state
中创建两个对象,播放列表和播放列表的索引,然后通过mutations
进行更新
state: {
// 播放列表
playlist:[{al:{}}],
// 当前播放的索引值
palyCurrentIndex:0,
},
getters: {
},
mutations: {
// 更新playlist
setPlaylist(state,value){
state.playlist = value
}
},
这里设置一个 al:{} 是防止初始化的时候报错
然后我们需要传递参数,我们知道播放列表中都是来自于歌单里面的歌,所以我们找到ListView.vue 详细歌单页面来进行参数传递
import {useStore} from 'vuex'
//setup中
const store = useStore()
//setup中的onMounted中
store.commit('setPlaylist',state.playlist.tracks)
然后我们在 playController.vue
中使用state中的值,我们可以看看是否拿的到值
<script>
import {useStore} from 'vuex'
export default {
setup(){
const store = useStore()
console.log(store);
return{
store
}
}
}
</script>
这里我们可以看到是拿到了,我们在模板上渲染,我们还需要看一下传过来的值如何取,我们需要取得是一个歌的图片,歌名,作者
这个我们之前在渲染歌单列表的时候使用过所以就是下面这几个
然后我就发现了,有些歌是两个人合作的或者多个人合作的,我渲染列表的时候没注意到,只取了第一个,所以要做一下修改。在 palylistview.vue
下,循环渲染一下。
<div class="anthor" >
<span v-for="(itemName,j) in item.ar" :key = "j">
<!-- 如果是最后一个就不加反斜杠 -->
<span v-if="j==item.ar.length-1">{{itemName.name}}</span>
<span v-else>{{itemName.name}}/</span>
</span>
<span class="album"> - {{item.al.name}}</span>
</div>
然后这里又出了一个新BUG,右边的图标被顶起来了,很难受。然后我们找到这个的样式,然后我调整了一下图标的大小
.right{
display: flex;
justify-content:center;
align-items:Center;
.icon{
width: 0.4rem;
height: 0.4rem;
opacity: 0.5;
margin-left: 15px;
}
}
回归正题,我们这里也需要循环渲染一下,这里我找了一个多人合作的歌
可以看到效果可以直接拿 palylistview.vue
的过来用。
<!-- 图片和名字部分 -->
<div class="left">
<img class="playImg" :src="$store.state.playlist[$store.state.palyCurrentIndex].al.picUrl" alt="">
<div class="content">
<div class="title">
<span>{{$store.state.playlist[$store.state.palyCurrentIndex].al.name}} - </span>
</div>
<div class="anthor">
<span v-for="(item,i) in $store.state.playlist[$store.state.palyCurrentIndex].ar " :key="i">
<!-- 如果是最后一个就不加反斜杠 -->
<span v-if="i==$store.state.playlist[$store.state.palyCurrentIndex].ar.length-1">{{item.name}}</span>
<span v-else>{{item.name}}/</span>
</span>
</div>
</div>
</div>
然后开始设置样式,还是从左到右,从图片开始先给图片取个名字,给它设置个大小,现在太大影响页面。
<img class="playImg" :src="$store.state.playlist[$store.state.palyCurrentIndex].al.picUrl" alt="">
.playImg{
width: 1rem;
height: 1rem;
}
先利用flex把所有的都变成横排列,并把它固定到页面底部
.playController{
width: 7.5rem;
padding: 0 0.4rem;
display: flex;
position: fixed;
left: 0;
bottom: 0;
justify-content: space-between;
// 左边部分
.left{
display: flex;
// 图片
.playImg{
width: 1rem;
height: 1rem;
}
// 内容
.content{
display: flex;
}
}
}
然后,调整图片是个圆形而且有外边框
.playImg{
width: 0.8rem;
height: 0.8rem;
border-radius: 100%;
border: 5px solid black;
}
然后调整字体,居中对齐,歌手名字变小。调整盒子间距
// 内容
.content{
display: flex;
padding-left: 10px;
align-items: center;
width: 4rem;
font-size: 14px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
.anthor{
font-size: 10px;
opacity: 0.5;
}
}
然后调整右边图标的大小、边距和居中
.right{
display: flex;
justify-content:center;
align-items:Center;
.icon{
width: 0.5rem;
height: 0.5rem;
margin-left: 15px;
}
}
然后在微调一下,给整体一个固定的高度,让它看起来没那么挤
.playController{
width: 7.5rem;
height: 1rem;
padding: 0 0.4rem;
display: flex;
justify-content: space-between;
}
然后这里给了一个固定的宽高后,也要让图片据中
.left{
display: flex;
justify-content:center;
align-items:Center;
}
这样看起来就好多了。
然后这里又有一个小BUG,就是在启动项目或者刷新页面的时候,第一次是没有值的,就像这样。
然后,我考虑了两个办法
利用v-if
在开始的时候不显示这个播放,也就是在playController
最外层的div上加
<div class="playController" v-if="$store.state.playlist[$store.state.palyCurrentIndex].al.picUrl">
</div>
效果大概就是下面这样
添加一个初始的数据
在state上添加一个初始的数据上去
state: {
// 播放列表
playlist:[{
al:{
name:"苦茶子",
picUrl:"http://p4.music.126.net/x8LI4oQGho-afSQFthr7wg==/109951167084000790.jpg",
},
ar:[{
name:"Starling8"
}]
}],
然后开始渲染的时候就会渲染自己自定义的数据,大家可以选一首自己喜欢的歌放上去
然后我们来实现获取音乐的效果,先看一下接口说明
我们可以看到,需要将audio 的src改成以下形式
<audio controls :src="https://music.163.com/song/media/outer/url?id=id.mp3"></audio>
那么id我们可以拿到,也就是下面这些,所以只需要稍微修改一下
<audio controls :src="`https://music.163.com/song/media/outer/url?id=${$store.state.playlist[$store.state.palyCurrentIndex].id}.mp3`"></audio>
这里我设置成 controls
是为了让播放按钮显示,用来测试一下,大家也可以试试,毕竟写了那么久,就为了听一下。
然后这里我们就需要修改一下我们在 state
上设置的默认值了
state: {
// 播放列表
playlist:[{
al:{
id: 73828956,
name: "年少",
pic: 109951163901435150,
picUrl: "http://p3.music.126.net/dHRFJJ9nfF_9YTxOz9UXUQ==/109951163901435148.jpg",
pic_str: "109951163901435148",
},
ar:[
{
id: 30284835,
name: "枯木逢春"},
],
id: 1316921651
}],
// 当前播放的索引值
palyCurrentIndex:0,
},
防止第一次渲染的时候没有数据,然后我们设置一个点击事件来触发audio,先利用ref
获取audio
元素
<!-- template -->
<svg class="icon" aria-hidden="true" @click="play">
<use xlink:href="#icon-bofang"></use>
</svg>
<audio ref="audio" :src="`https://music.163.com/song/media/outer/url?id=${$store.state.playlist[$store.state.palyCurrentIndex].id}.mp3`"></audio>
然后我们拿一下audio元素
import {ref} from 'vue'
const audio = ref(null)
function play(){
audio.value.play()
}
return{
store,
audio,
play
}
然后我们就可以点击播放按钮听到音乐了
然后我们可以输出一下 audio.value
,看一看什么在控制播放
我们可以看到就是它在改变控制开关,然后我们修改一下代码
function play(){
console.log(audio);
// 控制音乐的开关
if(audio.value.paused){
audio.value.play()
}else{
audio.value.pause()
}
}
这样就能实现音乐的开和关了,然后我们给他添加一个图标让它看起来更好看一点,找一个暂停的图标,利用v-show来进行判断,判断的条件我们自己自定义一个
<div class="right">
<!-- 播放与暂停 -->
<svg v-show="pause" class="icon" aria-hidden="true" @click="play">
<use xlink:href="#icon-bofang"></use>
</svg>
<svg v-show="!pause" class="icon" aria-hidden="true" @click="play">
<use xlink:href="#icon-zanting"></use>
</svg>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-24gf-playlistHeart"></use>
</svg>
</div>
// 控制图片切换
let pause = ref(true)
function play(){
// console.log(audio);
// 控制音乐的开关
if(audio.value.paused){
audio.value.play()
pause.value = false
}else{
audio.value.pause()
pause.value = true
}
}
return{
store,
audio,
pause,
play
}
然后接下来我们就来做切换歌曲,点击一下歌单列表达到换歌的效果,这里首先又排除一个BUG。
这些歌左边的播放按钮,其实是MV,有些歌是没有MV的
然后我在这个图标这里用v-if做了一个判断,如果它等于0就不再渲染它,修改的代码为playlistView.vue这个页面
<svg v-if="item.mv!=0" class="icon" aria-hidden="true">
<use xlink:href="#icon-bofang"></use>
</svg>
然后修复的时候,又发现一个小问题,就是歌名渲染错误了
然后我看了一下,应该是直接.name
<div class="title">
<span>{{$store.state.playlist[$store.state.palyCurrentIndex].name}} - </span>
</div>
这样就行了,然后回归正题,我们来做点击切换音乐的效果,首先我们要知道数据是从state
中来的,所以我们需要操作 state
。
然后我们再来看代码,是用id
来表达每个歌曲的,所以我们在mutations
只需要接收过来的值替换现有的id就可以实现效果。
// 切换歌曲
setPlayIndex(state,value){
state.palyCurrentIndex = value
}
然后在playlistView中我们需要点击的是一整块列表的div
我们给它绑定一个click
事件,传入它是列表的第几个
然后我们开始写点击事件
import {useStore} from 'vuex'
export default {
props:['playlist'],
setup(){
const store = useStore()
// 切换歌曲
function setIndex(index){
store.commit('setPlayIndex',index)
}
return {
store,
setIndex
}
}
}
这样我们的点击切换歌曲就写好了
然后我们看到这个图标是没有变化的,并且没有自动播放,这里我在state里面加上了这两个状态,肯定有其他方法,但我一直没实现,希望大家指点我一下。
这里修改的地方分别的 store的index.js
// state新增下面这两行数据
// 播放图片的显示和隐藏
pause:true,
// 歌曲是否自动播放
autoplay:false,
//mutations 加了一个方法,由于都是是否控制所以只传一个值取反
setAutoPlay(state,value){
state.pause = !value
state.autoplay = value
}
然后修改的地方 playController.vue
<!-- 播放按钮和列表按钮 -->
<div class="right">
<!-- 播放与暂停 -->
<svg v-show="$store.state.pause" class="icon" aria-hidden="true" @click="play">
<use xlink:href="#icon-bofang"></use>
</svg>
<svg v-show="!$store.state.pause" class="icon" aria-hidden="true" @click="play">
<use xlink:href="#icon-zanting"></use>
</svg>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-24gf-playlistHeart"></use>
</svg>
</div>
<!-- 获取音乐播放 -->
<audio :autoplay="$store.state.autoplay" ref="audio" :src="`https://music.163.com/song/media/outer/url?id=${$store.state.playlist[$store.state.palyCurrentIndex].id}.mp3`"></audio>
// 控制图片切换
function play(){
// console.log(audio);
// 控制音乐的开关
if(audio.value.paused){
audio.value.play()
store.state.pause = false
}else{
audio.value.pause()
store.state.pause = true
}
}
然后就是playlistView.vue
文件
const state = true
// 切换歌曲
function setIndex(index){
store.commit('setPlayIndex',index)
store.commit('setAutoPlay',state)
}
这样大部分就完成了,然后会有一个小问题,就是从歌单点了歌之后返回另一个歌单,会自动播放新歌单的第一首歌。然后我又在recommendMusic.vue给了一个click
事件
<router-link :to="{path:'/listview',query:{id:img.id}}" class="imgs" v-for="(img,index) in songlistResult.list" :key="index" @click="autoplay">
<img :src="img.picUrl"/>
<div class="count">
<span>▷</span>
<!--播放量 -->
<span>{{formatNum(img.playcount)}}</span>
</div>
<p>{{img.name}}</p>
</router-link>
import {useStore} from 'vuex'
const store = useStore()
const state = false
function autoplay(){
store.commit('setAutoPlay',state)
}
return {
autoplay
}
最后的效果