(1)此项目为一个前后台分离的仿网易云音乐项目的 SPA
(2)包括 发现音乐 / 视频 / 朋友 / 直播 / 私人FM 等功能模块
(3)前端:使用 Vue 全家桶 + iView + Axios + ES6 + Webpack 等技术
(4)后端:使用网易云音乐 API
(5)采用模块化、组件化、工程化的模式开发
网易云音乐 NodeJS 版 API (binaryify.github.io)
先看我实现的效果
背景是实时跟随的,并且是模糊的效果
正常来写 CSS 样式的时候,我不管怎么写,我的图片总是被后面的背景图片所覆盖,于是我只能用 :before 或 :after 伪元素来实现静态效果
但是,只是静态效果肯定不行,它需要实时的跟随图片而更换背景,但是我又想不到怎么用 Vue 来直接操作 :before 伪元素
但是,我想到了使用
类继承
的概念,让 :before 中的background-image
属性继承它的父类,它的父类就是demo-carousel
,直接把backgroundImage
属性写在内联样式中,即可实现此效果。
// 设置轮播图
.ivu-carousel {
height: 42%;
margin-bottom: 30px;
}
.demo-carousel {
position: relative;
height: 100%;
width: 100%;
img {
width: 730px;
border-radius: 8px;
position: absolute;
left: 430px;
border: 1px solid #fff;
box-shadow: 0px 0px 28px #888849;
}
&::before {
content: "";
position: absolute;
// inherit - 规定应该从父元素继承 background-image 属性的设置。
background-image: inherit;
width: 100%;
height: 120%;
left: 0px;
top: -26px;
background-size: 66666px;
background-position: center center;
filter: blur(20px);
}
&::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
}
}
先看我实现的效果
我选中项的字体会变大
如何实现?
首先写好让字体变大的 CSS 样式
// 字体变大
.fontBig {
font-weight: 800;
font-size: 18px !important;
}
然后我用的是 iview UI组件中的 Menu 组件
其中提供给了 @on-select 事件 - 选择菜单(MenuItem)时触发,返回选中项name的值,也就是返回当前路径
created(){
if(!sessionStorage.getItem("selectFoundMusicMenuPath")) return sessionStorage.setItem("selectFoundMusicMenuPath", "/")
this.selectMenuPath = sessionStorage.getItem("selectFoundMusicMenuPath")
},
// 当前 menuList 选中项事件
menuSelect(name){
// console.log(name);
// 把 name 直接赋值给 this.selectMenuPath
this.selectMenuPath = name
// 把 name 保存到 sessionStorage 中
sessionStorage.setItem("selectFoundMusicMenuPath", name)
},
用户选择相应的菜单项,就把 name(当前路径)保存到 sessionStorage 中,然后再从 sessionStorage 中拿出来保存到 selectMenuPath 中
判断如果当前路径为 selectMenuPath 的值,则当前选择菜单项字体变大,否则没有任何样式效果
先看我实现的效果
鼠标点击
个性推荐
页的推荐歌单
跳转至/songlist
页,即个性推荐
右边那个,并且推荐歌单
的字体还有相应的变大,并且,点击左右能够返回相应的页面,并且返回来之后对应的菜单项的字体变大,这要怎么实现呢?看我操作,直接操作 DOM 元素(虽然很笨,但是实现了就好~)
// 封装操作dom的函数,动态切换顶部导航
autoTopMenu(path) {
// 获取到当前页面的所有 class 为 ivu-menu-item 的元素
let menuItemElements = document.getElementsByClassName("ivu-menu-item")
// 定义 baseUrl
const baseUrl = "http://127.0.0.1:8080/#"
menuItemElements.forEach((item)=>{
// console.log(item.textContent.trim());
if(item.href === baseUrl + path) {
// console.log(item);
// 自动点击当前 dom 元素
item.click()
}
})
},
// 推荐歌单 - 点击跳转至推荐歌单页
geDanClick() {
// 调用操作dom的函数,动态切换顶部导航
this.autoTopMenu("/songlist")
},
首先获取到当前页面的所有 class 为 ivu-menu-item 的元素,进行遍历 ,判断这些元素中的 href 和是否和我传入的路径相等,如果相等,则自动点击当前 DOM 元素,实现,点击
个性推荐
页的推荐歌单
跳转至/songlist
页
然后就是点击返回按钮,实现页面的返回,返回之后的菜单项字体变大
// 实现导航栏跟随效果
autoClickMenu() {
// 获取到当前页面的所有 class 为 ivu-menu-item 的元素
let menuItemElements = document.getElementsByClassName("ivu-menu-item")
setTimeout(()=>{
// 获取当前 url
const url = "http://127.0.0.1:8080/#"+this.$router.history.current.path
// console.log(url);
menuItemElements.forEach((item)=>{
// console.log(item.textContent.trim());
if(item.href === url) {
// console.log(item);
// 自动点击当前 dom 元素
item.click()
}
})
}, 5)
},
封装一个操作 DOM 的函数,供返回和前进使用
关键点在于其中使用了 setTimeout ,我使其在跳转完页面后等待 5 毫秒之后再获取当前的 url,进行点击
goBack() {
this.$router.go(-1)
this.autoClickMenu()
},
goForward() {
this.$router.go(1)
this.autoClickMenu()
},
主要记录的是 如何实现单击事件和双击事件共存这件事
data() {
// 单击收藏按钮的延迟
collectTime: null,
/*
设置一个标志,证明你点击了收藏,禁止其他功能的使用
false - 没有点击到收藏按钮,点击该行可以操作其他功能
true - 点击到收藏按钮,不可以操作其他功能
*/
collectFlag: false
}
// 单击收藏音乐事件
collectSongClick(index) {
// 设置一个标志,证明你点击了收藏,禁止其他功能的使用
this.collectFlag = true
// console.log(index);
// 设置延迟,实现单击事件和双击事件共存的效果
this.collectTime = setTimeout(()=>{
if(this.playList[index].collectSong === "icon-shoucang") {
this.playList[index].collectSong = "icon-lujing"
this.$Message.success("已添加到我喜欢的音乐")
this.collectFlag = false
}
else {
this.playList[index].collectSong = "icon-shoucang"
this.$Message.success("取消喜欢成功")
this.collectFlag = false
}
}, 300)
},
// 双击收藏音乐事件
collectSongDblClick() {
// 取消上次延时未执行的方法
clearTimeout(this.collectTime)
},
解决方法:为单击事件设置延时,在双击的时候清除延时即可实现单击事件和双击事件共存了
还有就是,设置标志,实现在点击收藏按钮时不会触发 table 表格本身的行触发事件,在收藏操作完成后,才能继续执行该行的其他功能
如何让歌词有单独的滚动条呢,这个应该没那么简单吧?
其实就是为你需要滚动条的div元素上加上 3 行 CSS 代码就行了~
overflow: scroll;
height: 415px;
width: 460px;
虽然啥也不想说,但是还是记录一下吧,渲染评论回复搞了一天
首先要找对接口
http://127.0.0.1:3000/comment/floor?parentCommentId=5167695175&id=1498342485&type=0)
parentCommentId
- 就是歌曲评论 ID
id
- 资源 ID,也就是歌曲 ID
但是我需要歌曲评论 ID,所有我不能直接调用此接口,我还需要通过调用其他接口先获取到歌曲评论 ID,然后才能调用此接口
获取歌曲评论 ID 的接口是
/comment/music?id=1498342485
id
- 歌曲 ID
我测试的是 ‘耗尽’ 这首很火的歌, 返回数据如下:
{
"code":200,
"message":"获取成功",
"data":{
"ownerComment":{
"user":{
"locationInfo":null,
"liveInfo":null,
"anonym":0,
"userId":1786326513,
"avatarDetail":{
"userType":4,
"identityLevel":null,
"identityIconUrl":"https://p5.music.126.net/obj/wo3DlcOGw6DClTvDisK1/4874132307/4499/f228/d867/da64b9725e125943ad4e14e4c72d0884.png"
},
"userType":4,
"remarkName":null,
"vipRights":{
"associator":{
"vipCode":100,
"rights":true
},
"musicPackage":null,
"redVipAnnualCount":1,
"redVipLevel":5
},
"nickname":"音未弟弟",
"avatarUrl":"https://p1.music.126.net/tbGSLSVhakclgOmj-RRrMQ==/109951165511416658.jpg",
"authStatus":0,
"expertTags":null,
"experts":null,
"vipType":11
},
"beReplied":[
],
"pendantData":null,
"showFloorComment":null,
"status":0,
"commentId":5167746075,
"content":"“爱是需要双向奔赴的”",
"time":1606408473522,
"likedCount":55426,
"expressionUrl":null,
"commentLocationType":0,
"parentCommentId":0,
"decoration":{
},
"repliedMark":null,
"liked":false
},
"currentComment":null,
"comments":[
{
"user":{
"locationInfo":null,
"liveInfo":null,
"anonym":0,
"userId":372717414,
"avatarDetail":null,
"userType":0,
"remarkName":null,
"vipRights":null,
"nickname":"小高今天恋爱了嘛",
"avatarUrl":"https://p1.music.126.net/2QoP2H0JzNvmwAytKWA2GA==/109951164987398257.jpg",
"authStatus":0,
"expertTags":null,
"experts":null,
"vipType":0
},
"beReplied":[
{
"user":{
"locationInfo":null,
"liveInfo":null,
"anonym":0,
"userId":1786326513,
"avatarDetail":null,
"userType":4,
"remarkName":null,
"vipRights":null,
"nickname":"音未弟弟",
"avatarUrl":"https://p1.music.126.net/tbGSLSVhakclgOmj-RRrMQ==/109951165511416658.jpg",
"authStatus":0,
"expertTags":null,
"experts":null,
"vipType":11
},
"beRepliedCommentId":5167746075,
"content":"“爱是需要双向奔赴的”",
"status":0,
"expressionUrl":null
}
],
"pendantData":null,
"showFloorComment":null,
"status":0,
"commentId":5167757062,
"content":"他也是这么说的,可是双向奔赴的对象不是我。",
"time":1606408567365,
"likedCount":571,
"expressionUrl":null,
"commentLocationType":0,
"parentCommentId":5167746075,
"decoration":{
},
"repliedMark":null,
"liked":false
},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
Object{...},
{
"user":{
"locationInfo":null,
"liveInfo":null,
"anonym":0,
"userId":1973191562,
"avatarDetail":null,
"userType":0,
"remarkName":null,
"vipRights":null,
"nickname":"沧笙踏歌190907",
"avatarUrl":"https://p1.music.126.net/RLeBJe4D1ZzUtltxfoKDMg==/109951163250239066.jpg",
"authStatus":0,
"expertTags":null,
"experts":null,
"vipType":0
},
"beReplied":[
{
"user":{
"locationInfo":null,
"liveInfo":null,
"anonym":0,
"userId":1454937266,
"avatarDetail":null,
"userType":0,
"remarkName":null,
"vipRights":null,
"nickname":"薛紫陌",
"avatarUrl":"https://p1.music.126.net/p0WWF1UJvX5X7RMKpmO9cQ==/109951165383726395.jpg",
"authStatus":0,
"expertTags":null,
"experts":null,
"vipType":0
},
"beRepliedCommentId":5167864168,
"content":"双向离开是不是也算一种双向奔赴?",
"status":0,
"expressionUrl":null
}
],
"pendantData":null,
"showFloorComment":null,
"status":0,
"commentId":5167898082,
"content":"算吧,只不过奔向了两个不同的地方",
"time":1606434887054,
"likedCount":6,
"expressionUrl":null,
"commentLocationType":0,
"parentCommentId":5167746075,
"decoration":{
},
"repliedMark":null,
"liked":false
}
],
"hasMore":true,
"totalCount":426,
"time":1606434887054
}
}
然后渲染数据就行,
// 获取歌曲评论
async getPlayListComment() {
this.$Loading.start()
const {data: res} = await this.$http.get(`/comment/music?id=${this.songId}&limit=20`)
this.$Loading.finish()
// console.log("res: ",res);
this.hotCommentList = res.hotComments
this.commentList = res.comments
this.commentTotal = res.total
// console.log("this.hotCommentList :", this.hotCommentList);
/*
parentCommentId - 评论 id
id - 歌曲 id
type - 0: 歌曲
*/
res.hotComments.forEach(async (item)=>{
console.log("item: ", item);
const {data: res2} = await this.$http.get(`/comment/floor?parentCommentId=${item.commentId}&id=${this.songId}&type=0`)
// console.log("res2.data: ", res2.data);
this.hotCommentsData.push(res2.data)
})
res.comments.forEach(async (item)=>{
// console.log("item: ", item);
const {data: res2} = await this.$http.get(`/comment/floor?parentCommentId=${item.commentId}&id=${this.songId}&type=0`)
// console.log("res2.data: ", res2.data);
this.commentsData.push(res2.data)
})
// console.log("this.floorCommentContentList: ",this.floorCommentContentList);
// console.log("this.hotCommentsData: ", this.hotCommentsData);
// console.log("this.commentsData: ", this.commentsData);
// console.log("this.hotCommentList: ", this.hotCommentList);
},
最新评论({{ commentTotal }})
{{ item.ownerComment.user.nickname }}:
{{ item.content }}
{{ item.ownerComment.time | dateFormat }}
@{{ item2.user.nickname }}:
{{ item2.content }}
问题是如何重新加载页面,而使页面不“刷新”,我使用的是 inject 注入 reload 的方法
首先在
App.vue
中写上以下代码
在
router-view
中写上 v-if 判断,就是给一个重新加载的标志,默认为 true然后在 provide 中提供一个 reload 属性
在 method 中写上重新加载页面的方法,一开始让 if 为 false,然后通过
this.$nextTick()
方法,也就是在下一帧的时候立刻让 if 再变回 true,这是最关键的部分最后只需要在需要重新加载页面的组件写上
inject: ["reload"],
在需要触发时写上this.reload()
即可如下:
export default {
inject: ["reload"],
data() {
return {
mvData: {},
}
},
created() {
// 获取当前mvid
this.mvId = this.$router.history.current.query.id
this.getMv()
this.$forceUpdate()
},
methods: {
// 获取mv
async getMv() {
const {data: res} = await this.$http.get(`/mv/url?id=${this.mvId}`)
// console.log("res: ", res);
this.mvData = res.data
// console.log("mvData: ", this.mvData);
},
// 当视频无法播放时触发
errorHandle(event) {
console.log("event: ",event);
if(event.code == 2504) {
// location.reload()
// 重新加载页面
this.reload()
}
}
},
}
直接在
/router/index.js
文件中添加以下代码即可解决
Vue.use(VueRouter);
// 解决重复点击同一路由报错问题
//获取原型对象上的push函数
const originalPush = VueRouter.prototype.replace;
//修改原型对象中的push方法
VueRouter.prototype.replace = function replace(location) {
return originalPush.call(this, location).catch((err) => err);
};
为什么说路由”以假乱真“呢?其实是这样的
当你从一个地方点击一个进去,会进入 “/”
在我点击的这个电台事件里,有如下指定
this.$router.push(`/personal/fm?id=${this.userId}&index=${index}&rid=${rid}`)
但是我想要让它直接访问到 ”节目“ 栏的路由,这时候需要在 /router/index.js 中去在
/personal/fm
这个路由下重定向它的子路由/program
但是现在跳的话会是
/program?id=1&index=0&rid=795140368
,这样子确实也能拿到数据,但是却太长了,由于我想要的 ”节目“ 栏路由为/program?rid=795140368
所以我将 id 和 index 的值保存到了 sessionStorage 中,在电台点击事件里直接改成以下即可this.$router.push(`/personal/fm?rid=${rid}`)
这样你点进去就是
/program?rid=795140368
,于是就实现了路由 ”以假乱真“