No8.网易云音乐案例
知识点自测
- 知道reset.css和flexible.js的作用。
- 什么是组件库-例如bootstrap的作用。
- yarn命令的使用。
- 组件名字用name属性方式注册。
- 如何自定义组件库样式。
1.本地接口项目部署
下载网易云音乐node接口项目, 在本地启动, 为我们vue项目提供数据支持。
项目地址
备用地址
下载:git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git
下载后, 安装所有依赖: npm install 或者 yarn
,
在本地启动起来: node app.js 不能使用npm run serve启动,因为这不是vue的脚手架项目
,
测试访问此地址是否有数据:http://localhost:3000
, 看到如下页面就成功了。
总结: Node搭建的服务,如何把数据请求回来?前端请求本地的node项目, 收到请求后,node服务器伪装请求去拿网易云音乐服务器数据转发回给自己前端。(如何做反向代理解决跨域问题?本地node服务器开启cors,负责请求的转发和数据接收回传。)
学习目标
1.能够掌握vant组件库的使用。
2.能够掌握vant组件自定义样式能力。
3.能够掌握组件库使用和文档使用能力。
4.能够完成网易云音乐案例。
2.前端项目准备
2-1. 前端项目初始化
目标: 初始化项目, 下载必备包, 引入初始文件, 配置按需自动引入vant, 创建页面组件。
1》初始化工程:vue create music-demo
2》下载需要的所有第三方依赖包:yarn add axios vant vue-router
3》下载Vant自动按需引入插件:yarn add babel-plugin-import -D
- 本次vant使用自动按需引入的方式:https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart文档
- babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
4》在babel.config.js
配置–看Vant文档。
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
5》引入笔记代码里准备好的reset.css和flexible.js - 实现样式初始化和适配问题 - 引入到main.js中。
import "@/mobile/flexible" // 适配
import "@/styles/reset.css" // 初始化样式
2-2. 需求分析
根据需求, 创建路由所需要的5个页面的组件:
- Layout(布局, 顶部导航和底部导航) > 二级路由 Home 、Search和Play。
布局图
创建需要的views下的页面组件4个:
- src/views/Layout/index.vue - 负责布局(上下导航 - 中间二级路由切换首页和搜索页面)
<style scoped>
/* 中间内容区域 - 容器样式(留好上下导航所占位置) */
.main {
padding-top: 46px;
padding-bottom: 50px;
}
</style>
- views/Home/index.vue - 标题和歌名样式
/* 标题 */
.title {
padding: 0.266667rem 0.24rem;
margin: 0 0 0.24rem 0;
background-color: #eee;
color: #333;
font-size: 15px;
}
/* 推荐歌单 - 歌名 */
.song_name {
font-size: 0.346667rem;
padding: 0 0.08rem;
margin-bottom: 0.266667rem;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box; /** 对象作为伸缩盒子模型显示 **/
-webkit-box-orient: vertical; /** 设置或检索伸缩盒对象的子元素的排列方式 **/
-webkit-line-clamp: 2; /** 显示的行数 **/
overflow: hidden; /** 隐藏超出的内容 **/
}
- views/Search/index.vue
/* 搜索容器的样式 */
.search_wrap {
padding: 0.266667rem;
}
/*热门搜索文字标题样式 */
.hot_title {
font-size: 0.32rem;
color: #666;
}
/* 热搜词_容器 */
.hot_name_wrap {
margin: 0.266667rem 0;
}
/* 热搜词_样式 */
.hot_item {
display: inline-block;
height: 0.853333rem;
margin-right: 0.213333rem;
margin-bottom: 0.213333rem;
padding: 0 0.373333rem;
font-size: 0.373333rem;
line-height: 0.853333rem;
color: #333;
border-color: #d3d4da;
border-radius: 0.853333rem;
border: 1px solid #d3d4da;
}
- views/Play/index.vue - 直接从预习资料里复制(节省时间) - 可自己扩展阅读代码
<template>
<div class="play">
<!-- 模糊背景(靠样式设置), 固定定位 -->
<div
class="song-bg"
:style="`background-image: url(${
songInfo && songInfo.al && songInfo.al.picUrl
}?imageView&thumbnail=360y360&quality=75&tostatic=0);`"
></div>
<!-- 播放页头部导航 -->
<div class="header">
<van-icon
name="arrow-left"
size="20"
class="left-incon"
@click="$router.back()"
/>
</div>
<!-- 留声机 - 容器 -->
<div class="song-wrapper">
<!-- 留声机本身(靠css动画做旋转) -->
<div
class="song-turn ani"
:style="`animation-play-state:${playState ? 'running' : 'paused'}`"
>
<div class="song-img">
<!-- &&写法是为了防止报错, 有字段再继续往下访问属性 -->
<img
style="width: 100%"
:src="`${
songInfo && songInfo.al && songInfo.al.picUrl
}?imageView&thumbnail=360y360&quality=75&tostatic=0`"
alt=""
/>
</div>
</div>
<!-- 播放按钮 -->
<div class="start-box" @click="audioStart">
<span class="song-start" v-show="!playState"></span>
</div>
<!-- 播放歌词容器 -->
<div class="song-msg">
<!-- 歌曲名 -->
<h2 class="m-song-h2">
<span class="m-song-sname"
>{
{
songInfo.name }}-{
{
songInfo && songInfo.ar && songInfo.ar[0].name
}}</span
>
</h2>
<!-- 歌词部分-随着时间切换展示一句歌词 -->
<div class="lrcContent">
<p class="lrc">{
{
curLyric }}</p>
</div>
</div>
<!-- 留声机 - 唱臂 -->
<div class="needle" :style="`transform: rotate(${needleDeg});`"></div>
</div>
<!-- 播放音乐真正的标签
看接口文档: 音乐地址需要带id去获取(但是有的歌曲可能404)
https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=%e8%8e%b7%e5%8f%96%e9%9f%b3%e4%b9%90-url
-->
<audio
ref="audio"
preload="true"
:src="`https://music.163.com/song/media/outer/url?id=${id}.mp3`"
></audio>
</div>
</template>
<script>
// 获取歌曲详情和 歌曲的歌词接口
import {
getSongByIdAPI, getLyricByIdAPI } from '@/api'
import {
Icon } from 'vant'
export default {
components: {
[Icon.name]: Icon,
},
name: 'play',
data() {
return {
playState: false, // 音乐播放状态(true暂停, false播放)
id: this.$route.query.id, // 上一页传过来的音乐id
songInfo: {
}, // 歌曲信息
lyric: {
}, // 歌词枚举对象(需要在js拿到歌词写代码处理后, 按照格式保存到这个对象)
curLyric: '', // 当前显示哪句歌词
lastLy: '' // 记录当前播放歌词
}
},
computed: {
needleDeg() {
// 留声机-唱臂的位置属性
return this.playState ? '-7deg' : '-38deg'
}
},
methods: {
async getSong() {
// 获取歌曲详情, 和歌词方法
const res = await getSongByIdAPI(this.id)
this.songInfo = res.data.songs[0]
// 获取-并调用_formatLyr方法, 处理歌词
const lyrContent = await getLyricByIdAPI(this.id)
const lyricStr = lyrContent.data.lrc.lyric
this.lyric = this._formatLyr(lyricStr)
// 初始化完毕先显示零秒歌词
this.curLyric = this.lyric[0]
},
_formatLyr(lyricStr) {
// 可以看network观察歌词数据是一个大字符串, 进行拆分.
let reg = /\[.+?\]/g //
let timeArr = lyricStr.match(reg) // 匹配所有[]字符串以及里面的一切内容, 返回数组
console.log(timeArr); // ["[00:00.000]", "[00:01.000]", ......]
let contentArr = lyricStr.split(/\[.+?\]/).slice(1) // 按照[]拆分歌词字符串, 返回一个数组(下标为0位置元素不要,后面的留下所以截取)
console.log(contentArr);
let lyricObj = {
} // 保存歌词的对象, key是秒, value是显示的歌词
timeArr.forEach((item, index) => {
// 拆分[00:00.000]这个格式字符串, 把分钟数字取出, 转换成秒
let ms = item.split(':')[0].split('')[2] * 60
// 拆分[00:00.000]这个格式字符串, 把十位的秒拿出来, 如果是0, 去拿下一位数字, 否则直接用2位的值
let ss = item.split(':')[1].split('.')[0].split('')[0] === '0' ? item.split(':'<