B站看完一位老师讲的vue项目(悦听),是一个音乐播放器,花了两天时间完成了这个项目,第一次将前几天学习的vue知识应用到项目中,虽然有些地方还需要完善,但是通过这几天的学习,无论是vue入门,还是对html、css知识的复习,都很有收获。之前,每次网页布局的时候,有很多次,仅仅改变了一个元素的属性,页面整体发生了“很奇怪”的变化,很有挫败感,这几天重新学习了css中有关文档流、浮动、定位、弹性盒字等知识,感觉豁然开朗。
前两天学习了vue,仅仅算是入门了,主要是一些vue指令和axios的使用,只是简单地使用vue指令做的小应用是本地应用的“小黑记事本”,结合axios做的网络应用有“天知道”和这篇文章要介绍的音乐播放器“悦听”,废话不多说,进入正题。。(如果功能有完善,后续更新…)
一、项目需求
主界面:
mv界面:
从上到下,分别是搜索栏、主体部分、音频进度条,主体部分从左到右分别是音乐列表、磁盘动画、留言列表。
主要功能有:
1.歌曲搜索:搜索栏中输入关键字,按下回车或者鼠标点击输入框后面的放大镜图标,返回给左侧音乐列表所有相关的歌曲名称。
2.歌曲播放:点击音乐列表中每条音乐前面的红色播放图标实现播放功能,下方进度条显示当前播放进度。
3.查看热评:歌曲播放的同时,右侧评论列表展示所有热评。
4.歌曲播放时的动画:歌曲播放的同时,磁盘中央封面改成相对应的专辑封面,上面的磁头旋转到磁盘上,歌曲暂停后,磁头再旋转回去。
5.mv播放:点击音乐列表中右侧的小电视图标,展现出四周由遮罩层包含的mv界面。
6.mv暂停:点击四周遮罩层,mv暂停,回到主界面。
二、相关接口
1.歌曲搜索接口
请求地址:https://autumnfish.cn/search
请求方法:get
请求参数:keywords(查询关键字)
响应内容:歌曲搜索结果
2.歌曲url获取接口
请求地址:https://autumnfish.cn/song/url
请求方法:get
请求参数:id(歌曲id)
响应内容:歌曲url地址
3.歌曲详情获取
请求地址:https://autumnfish.cn/song/detail
请求方法:get
请求参数:ids(歌曲id)
响应内容:歌曲详情(包括封面信息)
4.热门评论获取
请求地址:https://autumnfish.cn/comment/hot?type=0
请求方法:get
请求参数:id(歌曲id,地址中的type固定为0)
响应内容:歌曲的热门评论
5.mv地址获取
请求地址:https://autumnfish.cn/mv/url
请求方法:get
请求参数:id(mvid,为0表示没有mv)
响应内容:mv的地址
三、代码实现
html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>悦听</title>
<link rel="stylesheet" href="my.css"/>
</head>
<body>
<div class="wrap">
<div class="play_wrap" id="player">
<!--搜索栏-->
<div class="search_bar">
<div class="title">悦听</div>
<div class="search">
<input @keyup.enter="searchMusic" v-model="query" type="text" class="content" />
<img @click="searchMusic" class="loupe" src="images/zoom.png" />
</div>
</div>
<!--主体部分-->
<div class="center_con">
<div class="music_list">
<ul class="music">
<li v-for="(item,index) in musicName" class="music_li">
<a @click="playMusic(item.id)" href="#">
<img class="music_btn" src="./images/play.png" />
</a>
<span class="music_name">{{item.name}}</span>
<span v-show="item.mvid!=0" class="mv_btn">
<img @click="getMv(item.mvid)" class="mvtable" src="images/table2.png" />
</span>
</li>
</ul>
</div>
<div>
<img class="left_line" src="images/line.png" />
</div>
<div class="pic">
<div class="top" :class="{playing:isPlaying}">
<img class="player" src="images/player_bar.png" />
</div>
<div class="center">
<img class="disc" src="images/disc.png" />
<!--不能有两个src(一个默认的,一个是当前歌的封面),要用条件表达式-->
<img :src="picUrl==''?'./images/cover.png':picUrl" class="cover"/>
</div>
</div>
<div>
<img class="right_line" src="images/line.png" />
</div>
<div class="comment_list_outer">
<b class="comm">热门留言</b><br />
<div class="comment_list">
<ul class="comment">
<li v-for="(item,index) in commentList" class="comment_li">
<img class="user_pic" :src="item.user.avatarUrl" />
<div class="comm_right">
<b class="user_nickname">{{item.user.nickname}}</b><br />
<span class="user_comm">{{item.content}}</span>
</div>
</li>
</ul>
</div>
</div>
</div>
<!--播放进度音频-->
<div class="audio_con">
<audio :src="musicUrl" @play="play" @pause="pause" class="audio" autoplay controls loop></audio>
</div>
<!--视频-->
<div class="video_con" v-show="isShow">
<!--ref-->
<video ref='video' :src="mvUrl" autoplay controls loop></video>
<div class="mask" @click="closeMv"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="my.js"></script>
</body>
</html>
css:
*{
margin: 0;
padding: 0;
list-style: none;
}
.wrap{
width: 100vw;
height: 100vh;
overflow: hidden;
background:url(./images/bg.jpg) no-repeat;
background-size: 100% 100%;
}
.play_wrap{
width: 800px;
height: 520px;
margin: 77px auto;
}
.search_bar{
position: relative;
z-index: 4;
border-radius: 5px 5px 0 0;
height: 60px;
display: flex;
background-color: dodgerblue;
}
.title{
flex: 1;
padding-top: 10px;
padding-left: 15px;
color: white;
line-height:40px;
font-size: 30px;
font-family: "隶书";
}
.search{
flex:1;
line-height:60px;
/*当flex:8的时候,为啥padding-left值越大,听字会被盖住,因为padding的值会影响整个div的可见区*/
/*浏览器检察元素的宽高是可视区的宽高,包括边框、内边距、内容区,不包括外边框*/
/*flex如果都设置成1,只是代表width之比是1:1*/
/*当前的情况:(flex为1:1时)设两个弹性元素的width为x,则有2*x+padding-left=500-5px(悦听左边的边距)*/
padding-left: 240px;
/*background-color: blue;*/
}
.loupe{
position: relative;
top: -53px;
left: 230px;
}
.content{
border: none;
outline: none;
width: 250px;
height: 30px;
padding-left: 10px;
border-radius: 15px;
}
.center_con{
height: 445px;
display: flex;
background-color: rgb(255,255,255,.5);
}
.music_list{
flex: 6;
overflow-y: auto;
overflow-x: hidden;
}
.music_list::-webkit-scrollbar,.comment_list::-webkit-scrollbar{
display: none;
}
.music{
padding:5px;
}
.music_li:nth-of-type(odd){
display: flex;
padding: 3px;
line-height: 30px;
font-size: 12px;
}
.music_li:nth-of-type(even){
display: flex;
padding: 3px;
line-height: 30px;
font-size: 12px;
background-color: rgba(240, 240, 240, 0.3);
}
.comment_li{
display: flex;
padding: 10px;
line-height: 20px;
font-size: 15px;
}
.music_btn{
width: 17px;
height: 17px;
padding-top: 7px;
flex:2 ;
}
.music_name{
flex:5 ;
margin-left: 12px;
}
.mv_btn{
flex:2 ;
/**/
/*background: url("./images/table2.png");*/
}
.mvtable{
padding-top: 10px;
margin-left: 15px;
width: 18px;
height: 18px;
}
.left_line,.right_line{
width: 1px;
height: 440px;
}
.pic{
flex: 12;
}
.player{
position: absolute;
top: 125px;
left: 680px;
z-index: 3;
transform: rotate(-45deg);
transform-origin: 12px 12px;
transition: 1s;
}
.playing .player{
transform: rotate(0);
}
.disc{
position: absolute;
width: 265px;
height: 265px;
top: 200px;
left: 570px;
z-index: 2;
}
.cover{
position: absolute;
width: 160px;
height: 160px;
top: 250px;
left: 620px;
z-index: 1;
}
.comment_list_outer{
flex:6;
padding: 8px;
}
.comment_list{
height: 415px;
overflow-y: auto;
overflow-x: hidden;
}
.user_pic{
width: 40px;
height: 40px;
border-radius: 50%;
}
.comm_right{
margin: 7px;
}
.user_comm{
font-size: 12px;
color: #666;
}
.audio_con{
height: 35px;
}
.audio{
outline: none;
width: 800px;
height: 45px;
border-radius:0 0 5px 5px;
background-color: rgb(241, 243, 244);;
}
.mask{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 980;
background-color: rgb(0,0,0,.8);
}
video{
position: fixed;
left:250px;
top: -10px;
width: 870px;
height: 670px;
/*transform: translateX(-50%);*/
z-index: 990;
}
js:
var music=new Vue({
el:"#player",
data:{
query:"",
musicName:[],
musicUrl:"",
picUrl:"",
commentList:[],
mvUrl:"",
isShow:false,
isPlaying:false
},
methods:{
searchMusic:function(){
var that=this;
axios.get("https://autumnfish.cn/search?keywords="+this.query)
.then(
function(res){
that.musicName=res.data.result.songs;
// console.log(res);
})
.catch(function(err){console.log(err);})
},
playMusic:function(musicId){
var that=this;
axios.get("https://autumnfish.cn/song/url?id="+musicId)
.then(
function(res){
that.musicUrl=res.data.data[0].url;
})
.catch(function(err){console.log(err);})
axios.get("https://autumnfish.cn/song/detail?ids="+musicId)
.then(
function(res){
that.picUrl=res.data.songs[0].al.picUrl;
})
.catch(function(err){console.log(err);})
axios.get("https://autumnfish.cn/comment/hot?type=0&id="+musicId)
.then(
function(res){
console.log(res);
that.commentList=res.data.hotComments;
})
.catch(function(err){console.log(err);})
},
getMv:function(mvid){
var that=this;
that.isShow=true;
axios.get("https://autumnfish.cn/mv/url?id="+mvid)
.then(
function(res){
that.mvUrl=res.data.data.url;
})
.catch(function(err){console.log(err);})
},
closeMv:function(){
this.isShow=false;
//$refs
this.$refs.video.pause();
},
play:function(){
this.isPlaying=true;
},
pause:function(){
this.isPlaying=false;
}
}
})