网易云音乐项目

网易云音乐项目

壹之章 - 准备

1. 项目描述

(1)此项目为一个前后台分离的仿网易云音乐项目的 SPA

(2)包括 发现音乐 / 视频 / 朋友 / 直播 / 私人FM 等功能模块

(3)前端:使用 Vue 全家桶 + iView + Axios + ES6 + Webpack 等技术

(4)后端:使用网易云音乐 API

(5)采用模块化、组件化、工程化的模式开发

2. 项目功能界面

3. 技术选型

网易云音乐项目_第1张图片

4. 前端路由

网易云音乐项目_第2张图片

5. API/接口

网易云音乐项目_第3张图片

网易云音乐 NodeJS 版 API (binaryify.github.io)

问题 1 记录:轮播图

先看我实现的效果

背景是实时跟随的,并且是模糊的效果

正常来写 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%;
    }
}

问题 2 记录:导航栏

先看我实现的效果

我选中项的字体会变大

如何实现?

首先写好让字体变大的 CSS 样式

// 字体变大
.fontBig {
    font-weight: 800;
    font-size: 18px !important;
}

然后我用的是 iview UI组件中的 Menu 组件



    
        {{ item.title }}
    

其中提供给了 @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 的值,则当前选择菜单项字体变大,否则没有任何样式效果

问题 3 记录:动态切换菜单项

先看我实现的效果

鼠标点击 个性推荐 页的 推荐歌单 跳转至 /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()
},

问题 4 记录:收藏按钮实现点击切换效果

主要记录的是 如何实现单击事件和双击事件共存这件事


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 表格本身的行触发事件,在收藏操作完成后,才能继续执行该行的其他功能

问题 5 记录:歌词滚动条样式

如何让歌词有单独的滚动条呢,这个应该没那么简单吧?

其实就是为你需要滚动条的div元素上加上 3 行 CSS 代码就行了~

overflow: scroll;
height: 415px;
width: 460px;

问题 6 记录:评论回复应该如何渲染

虽然啥也不想说,但是还是记录一下吧,渲染评论回复搞了一天

首先要找对接口

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 }}
举报 | {{ item.likedCount }} | |

问题 7 记录:vue需要刷新一次页面才能正常加载?

问题是如何重新加载页面,而使页面不“刷新”,我使用的是 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()
            }
        }
    },
}

问题 8 记录:vue路由 重复点击报错 ?

直接在 /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);
};

问题 9 记录:vue路由 “以假乱真” ?

为什么说路由”以假乱真“呢?其实是这样的

当你从一个地方点击一个进去,会进入 “/”

网易云音乐项目_第4张图片

在我点击的这个电台事件里,有如下指定

this.$router.push(`/personal/fm?id=${this.userId}&index=${index}&rid=${rid}`)

网易云音乐项目_第5张图片

但是我想要让它直接访问到 ”节目“ 栏的路由,这时候需要在 /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 ,于是就实现了路由 ”以假乱真“

你可能感兴趣的:(Vue,vue)