vue项目
简介
- 这个项目仅用于练习vue,功能并未完善,bug也非常多,目前已完成主页、歌单列表、音乐播放、歌词;如果你想运行项目,需要下载musicapp与NeteaseCloudMusicApi-master,分别注入依赖后同时运行;素材不下载也没关系,并不妨碍运行项目;如果你希望加入新功能,可以点击下方文档
- 点我进入网易云音乐项目教学视频
- 点我进入网易云音乐NodeJS版API文档
如果链接失效,谷歌搜索网易云音乐NodeJS版API
脚手架创建musicapp项目
- 创建musicapp项目
1
2
3
4
5
6
7// 使用vue脚手架生成
vue create musicapp
// 多选router、vuex、less
cd musicapp
npm run serve
检测视窗宽度自适应字体大小
- public/js/rem.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const remSize = () => {
// 获取视窗宽度
let deviceWidth = document.documentElement.clientWidth || window.innerWidth;
// 检测视窗宽度并设置最大值与最小值
if(deviceWidth >= 750){
deviceWidth = 750;
}
if(deviceWidth <= 320){
deviceWidth = 320;
}
// 设计稿750px,那么1rem = 100px
// 设计稿375px,那么1rem = 50px
document.documentElement.style.fontSize = (deviceWidth / 7.5) + 'px';
// 设置字体大小
document.querySelector('body').style.fontSize = 0.3 + 'rem';
}
remSize()
window.onresize = function (){
remSize()
} - 将rem.js导入到public/index.html,位置在
</body>
的上面1
2
3
4<body>
...
<script src="<%= BASE_URL %>js/rem.js"></script>
</body>
加载字体图标
- 字体图标放到src/assets,打开demo_index.html查看字体图标类名
- main.js导入
import './assets/iconfont/iconfont.js'
- App.vue添加公共样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<router-view/>
</template>
<style lang="less">
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
.icon {
width: 1rem;
height: 1rem;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style> - 测试字体图标能否显示,位置在HomeView.vue
1
2
3
4
5
6
7<template>
<div class="home">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-liebiao2"></use>
</svg>
</div>
</template>
发现页面
顶部栏
- src/views/HomeView.vue导入TopNav组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div class="home">
<!--顶部栏-->
<top-nav></top-nav>
</div>
</template>
<script>
// @ is an alias to /src
import TopNav from '@/components/HomeView/TopNav.vue'
export default {
name: 'HomeView',
components: {
TopNav
}
}
</script> - src/components/HomeView创建TopNav.vue组件
个人习惯用大组件命名创建文件夹,在往里创建子组件,这样方便管理子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58<template>
<div class="topNav">
<div class="topLeft">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-liebiao2"></use>
</svg>
</div>
<div class="topCenter">
<span class="navBtn">我的</span>
<span class="navBtn active">发现</span>
<span class="navBtn">云村</span>
<span class="navBtn">视频</span>
</div>
<div class="topRight">
<svg class="icon search" aria-hidden="true">
<use xlink:href="#icon-sousuo"></use>
</svg>
</div>
</div>
</template>
<script>
export default {
name: "TopNav"
};
</script>
<style scoped lang="less">
.topNav{
width: 7.5rem;
height: 1rem;
display: flex;
//首个元素在起点,末尾元素在终点
justify-content: space-between;
//垂直居中
align-items: center;
padding: 0 0.2rem;
.icon{
width: 0.4rem;
height: 0.4rem;
}
.search{
width: 0.47rem;
height: 0.47rem;
}
.topCenter{
width: 4.5rem;
color: #C0C0C0;
display: flex;
// 平均分布
justify-content: space-around;
.active{
color: black;
font-weight: 900;
}
}
}
</style>
轮播图
- 安装swiper插件,实现触摸滑动功能
npm install swiper@5 --save
- src/views/HomeView.vue导入Banner组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<div class="home">
<!--顶部栏-->
<top-nav></top-nav>
<!--轮播图-->
<banner></banner>
</div>
</template>
<script>
// @ is an alias to /src
import TopNav from '@/components/HomeView/TopNav.vue'
import Banner from '@/components/HomeView/Banner.vue'
export default {
name: 'HomeView',
components: {
TopNav,
Banner,
},
}
</script> - src/assets/img存放banner.webp,图片可以是电脑壁纸,大小尺寸无所谓
- src/components/HomeView创建Banner.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76<template>
<div id="swipercom">
<div class="swiper-container" id="swiperIndex">
<div class="swiper-wrapper">
<!--循环获取轮播图-->
<div class="swiper-slide" v-for="(item, i) in imgs" :key="i">
<img :src="item.pic">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
</div>
</template>
<script>
import 'swiper/css/swiper.css'
import Swiper from 'swiper';
import axios from 'axios';
import { getBanner } from '@/api/index.js'
export default {
name: 'Banner',
data(){
return{
// 至少准备1份本地图片,避免出现问题
// require是动态获取本地图片
// pic是后端请求banners的格式
imgs:[
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
{ pic: require('../assets/img/banner.webp') },
]
}
},
async mounted(){
// 异步请求轮播图,1是请求android资源类型
let res = await getBanner(1);
// 查看res数据
// console.log(res)
// 请求到的轮播图会替换imgs
this.imgs = res.data.banners;
const mySwiper = new Swiper('#swiperIndex', {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable: true,
},
});
}
};
</script>
<style lang="less">
#swipercom{
width: 7.5rem;
#swiperIndex.swiper-container{
width: 7.1rem;
height: 2.6rem;
border-radius: 0.1rem;
.swiper-slide img{
width: 100%;
}
.swiper-pagination-bullet-active{
background-color: orangered;
}
}
}
</style> - src/api创建index.js,用来管理请求数据
1
2
3
4
5
6
7
8
9import axios from 'axios';
let baseUrl = 'http://localhost:3000'
// 获取轮播图的api, type:资源类型, 对应以下类型, 默认为0即PC
// 1: android; 2: iphone; 3: ipad
export let getBanner = (type=0) => {
return axios.get(`${baseUrl}/banner?type=${type}`)
} - 打开控制台,cd到NeteaseCloudMusicApi-master,运行
node app.js
,就能获取到后端发来的banner数据
图标列表
- src/views/HomeView.vue导入IconList组件
1
2
3
4
5
6
7
8
9
10<!--图标列表-->
<icon-list></icon-list>
import IconList from '@/components/HomeView/IconList.vue'
components: {
TopNav,
Banner,
IconList
} - src/components/HomeView创建IconList.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63<template>
<div class="iconList">
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-geinituijian"></use>
</svg>
<span>每日推荐</span>
</div>
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-shouyinji"></use>
</svg>
<span>私人FM</span>
</div>
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-yinlegedan-"></use>
</svg>
<span>歌单</span>
</div>
<div class="iconItem leaderboard">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-paihangbang-"></use>
</svg>
<span>排行榜</span>
</div>
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-zhibo"></use>
</svg>
<span>直播</span>
</div>
</div>
</template>
<script>
export default {
name: "IconList"
};
</script>
<style scoped lang="less">
.iconList{
display: flex;
justify-content: space-around;
padding: 0.4rem;
.iconItem{
display: flex;
// 垂直布局
flex-direction: column;
// 垂直居中
align-items: center;
.icon{
width: 0.8rem;
height: 0.8rem;
}
span{
padding: 0.1rem;
font-size: 0.2rem;
}
}
}
</style>
发现好歌单
- src/views/HomeView.vue导入MusicList组件
- src/components/HomeView创建MusicList.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128<template>
<div class="musicList">
<div class="musicList-top">
<div class="title">发现好歌单</div>
<div class="more">查看更多</div>
</div>
<div class="mlist">
<div class="swiper-container" id="musicSwiper">
<div class="swiper-wrapper">
<!--循环获取封面、播放次数、歌单名-->
<div class="swiper-slide" v-for="(item, i) in state.musicList" :key="i">
<img :src="item.picUrl" :alt="item.name">
<div class="count">
<span>▷ {{changeValue(item.playCount) }}</span>
</div>
<div class="name">{{item.name}}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import 'swiper/css/swiper.css'
import Swiper from 'swiper';
import { getMusicList } from '@/api/index.js'
import { reactive, onMounted, onUpdated } from 'vue'
export default {
name: 'MusicList',
setup(){
// 用来存放请求数据的空数组
let state = reactive({ musicList: [] });
// 过滤播放次数
let changeValue = (num) =>{
let res = 0;
if(num >= 100000000){
res = (num / 100000000).toFixed(0) + '亿';
}else if(num > 10000){
res = (num / 10000).toFixed(0) + '万';
}
return res;
}
// 请求'网友精选碟歌单'数据,并存进musicList数组内
onMounted(async ()=>{
let result = await getMusicList();
// 查看result数据
// console.log(result)
state.musicList = result.data.result;
})
// 控制歌单滑动功能,歌单显示3个,总共有10个
onUpdated(()=>{
const swiper = new Swiper('#musicSwiper', {
slidesPerView: 3,
spaceBetween: 10,
});
})
return {
state, changeValue
}
}
}
</script>
<style lang="less" scoped>
.musicList{
width: 7.5rem;
padding: 0 0.4rem;
// 发现好歌单与查看更多
.musicList-top{
display: flex;
justify-content: space-between;
height: 1rem;
align-items: center;
.title{
font-size: 0.4rem;
font-weight: 900;
}
.more{
border: 1px solid #ccc;
border-radius: 0.2rem;
font-size: 0.24rem;
height: 0.5rem;
width: 1.2rem;
text-align: center;
line-height: 0.5rem;
}
}
// 封面、播放次数、歌单名
.mlist{
.swiper-container{
width: 100%;
height: 3rem;
.swiper-slide{
display: flex;
flex-direction: column;
position: relative;
// 封面
img{
width: 100%;
height: auto;
border-radius: 0.1rem;
}
// 播放次数
.count{
position: absolute;
right: 0.1rem;
top: 0.1rem;
font-size: 0.24rem;
color: white;
display: flex;
align-items: center;
}
// 歌单名
.name{
width: 100%;
height: 0.9rem;
padding: 0.1rem 0;
overflow: hidden;
font-size: 0.24rem;
line-height: 0.4rem;
}
}
}
}
}
</style> - src/api/index.js添加请求歌单数据
1
2
3
4
5
6...
// 获取推荐歌单,可选参数limit:取出数量,默认为10
export let getMusicList = (limit=10) => {
return axios.get(`${baseUrl}/personalized?limit=${limit}`)
} - 预览效果
歌单详情页面
设置路由跳转
- src/views创建ListView.vue,内容写个你好,用来测试跳转
1
2
3<template>
<h1>你好</h1>
</template> - src/router打开index.js,添加ListView.vue的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/listview',
name: 'listview',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/ListView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router - src/components/HomeView/MusicList.vue修改div标签成router-link,并添加跳转路径,query是获取歌单id,根据id显示相应的内容
1
2
3
4
5
6
7<!--循环获取封面、播放次数、歌单名-->
<router-link :to="{ path:'/listview', query:{ id:item.id} }"
class="swiper-slide"
v-for="(item, i) in state.musicList" :key="i"
>
...
</router-link> - src/App.vue添加a标签公共样式
1
2
3
4
5
6
7<style lang="less">
...
a{
text-decoration: none;
color: #3e4149;
}
</style> - src/api/index.js添加请求歌单详情
1
2
3
4
5...
// 获取歌单详情
export let getPlaylistDetail = (id) => {
return axios.get(`${baseUrl}/playlist/detail?id=${id}`)
} - src/views/ListView.vue添加歌单详情数据,顺便把ListViewTop组件注册进去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47<template>
<div class="listView">
<!--歌单详情上部分,将数据传递给子组件-->
<list-view-top :playlist="state.playlist"></list-view-top>
</div>
</template>
<script>
import ListViewTop from "@/components/ListView/ListViewTop.vue";
import { getPlaylistDetail } from "@/api/index.js";
import { onMounted, reactive } from 'vue'
import { useRoute } from 'vue-router'
export default {
name: "ListView",
components: {
ListViewTop
},
setup(){
// 获取当前路径
const route = useRoute();
// playlist用来存放歌单详情数据,creator用来存放作者数据
// 不设置空对象,存放的数据在调用时会报undefined
let state = reactive({
playlist: {
creator: {}
}
});
onMounted(async () => {
// 查看route,寻找到id后存到变量id里
// console.log(route)
let id = route.query.id;
// 请求获取歌单详情数据
let result = await getPlaylistDetail(id);
// 查看歌单详情数据
console.log(result)
// 将得到的数据存进入
state.playlist = result.data.playlist;
})
return { state };
}
};
</script>
<style scoped>
</style>
歌单详情上部分
- src/components/ListView创建ListViewTop.vue组件
创建ListView文件夹,用来管理歌单详情组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219<template>
<div class="listViewTop">
<div class="listViewTopNav">
<!--背景图,动态获取图片路径-->
<img class="bg" :src="playlist.coverImgUrl">
<!--顶部左边,返回键与标题-->
<div class="left">
<!--点击三角图标返回到主页-->
<svg class="icon" aria-hidden="true"
@click="$router.go(-1)"
>
<use xlink:href="#icon-zuojiantou"></use>
</svg>
<div class="title">歌单</div>
</div>
<!--顶部右边,搜索与详情-->
<div class="right">
<svg class="icon search" aria-hidden="true">
<use xlink:href="#icon-sousuo"></use>
</svg>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-gengduo-shuxiang"></use>
</svg>
</div>
</div>
<div class="content">
<div class="contentLeft">
<!--封面-->
<img :src="playlist.coverImgUrl">
<!--播放次数-->
<span>▷ {{changeValue(playlist.playCount)}}</span>
</div>
<div class="contentRight">
<!--歌单名-->
<h3>{{playlist.name}}</h3>
<!--作者头像与名字-->
<div class="author">
<img class="header" :src="playlist.creator.avatarUrl" alt="">
<span>{{playlist.creator.nickname}}</span>
</div>
<!--描述内容-->
<div class="description">{{playlist.description}}</div>
</div>
</div>
<div class="iconList">
<!--评论-->
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-pinglun"></use>
</svg>
<span>{{playlist.commentCount}}</span>
</div>
<!--分享-->
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fenxiang-"></use>
</svg>
<span>{{playlist.commentCount}}</span>
</div>
<!--下载-->
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xiazai"></use>
</svg>
<span>下载</span>
</div>
<!--多选-->
<div class="iconItem">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-duoxuan-"></use>
</svg>
<span>多选</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ListViewTop",
// 接收父组件提供的playlist数据
props: ['playlist'],
setup(){
let changeValue = (num) =>{
let res = 0;
if(num >= 100000000){
res = (num / 100000000).toFixed(0) + '亿';
}else if(num > 10000){
res = (num / 10000).toFixed(0) + '万';
}
return res;
}
return { changeValue }
}
};
</script>
<style scoped lang="less">
.listViewTop{
width: 7.5rem;
padding: 0 0.4rem;
.bg{
position: fixed;
left: 0;
top: 0;
width: 7.5rem;
height: auto;
// 设置-1减少层数使背景不会遮住内容
z-index: -1;
// 添加滤镜使背景模糊,降低对比度
filter: blur(0.2rem) contrast(25%);
}
.listViewTopNav{
display: flex;
justify-content: space-between;
align-items: center;
height: 1.2rem;
font-size: 0.4rem;
padding-top: 0.3rem;
.left, .right{
display: flex;
color: #fff;
.icon{
width: 0.5rem;
height: 0.5rem;
// SVG修改颜色
fill: #fff;
}
.title{
margin-left: 0.4rem;
}
.search{
margin-right: 0.4rem;
}
}
}
.content{
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
.contentLeft{
position: relative;
img{
width: 2.8rem;
height: 2.8rem;
border-radius: 0.1rem;
}
span{
position: absolute;
right: 0.1rem;
top: 0.1rem;
font-size: 0.24rem;
color: white;
display: flex;
align-items: center;
}
}
.contentRight{
width: 3.5rem;
h3{
margin-top: 0.05rem;
color: white;
overflow: hidden;
text-overflow: ellipsis;
// 下面3个必须写,可以实现2行文字
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.author{
display: flex;
align-items: center;
margin: 0.25rem 0;
.header{
width: 0.6rem;
height: 0.6rem;
border-radius: 0.3rem;
margin-right: 0.2rem;
}
span{
font-size: 0.28rem;
font-weight: 500;
color: rgb(204, 204, 204);
}
}
.description{
font-size: 0.24rem;
color: rgba(204, 204, 204, 0.9);
// color: rgba(0, 0, 0, 0.4);
overflow: hidden;
text-overflow: ellipsis;
// 下面3个必须写,可以实现2行文字
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
}
.iconList{
display: flex;
justify-content: space-around;
.iconItem{
display: flex;
flex-direction: column;
align-items: center;
.icon{
width: 0.6rem;
height: 0.6rem;
fill: rgba(255, 255, 255, 0.9);
}
span{
font-size: 0.05rem;
padding-top: 0.05rem;
color: rgba(255, 255, 255, 0.9);
}
}
}
}
</style>
歌单详情下部分
- ListView.vue导入playList组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<play-list :playlist="state.playlist"></play-list>
import PlayList from "@/components/ListView/PlayList.vue";
components: {
ListViewTop,
PlayList
}
setup(){
...
// playlist用来存放歌单详情数据,creator用来存放作者数据,tracks存放歌曲
// 不设置空对象,存放的数据在调用时会报undefined
let state = reactive({
playlist: {
creator: {},
tracks: []
}
});
...
} - src/components/ListView创建PlayList.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172<template>
<div class="playlist">
<div class="playlist-top">
<!--播放全部功能-->
<div class="left">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-bofang"></use>
</svg>
<div class="text">
<div class="title">播放全部</div>
<div class="num">(共{{playlist.tracks.length}}首)</div>
</div>
</div>
<!--收藏功能-->
<div class="btn">+ 收藏({{changeValue(playlist.subscribedCount)}})</div>
</div>
<div class="list">
<!--循环歌曲数据-->
<div class="playItem"
v-for="(item, i) in playlist.tracks"
:key="i"
>
<div class="left">
<!--索引-->
<div class="index">{{i + 1}}</div>
<div class="content">
<!--歌曲名-->
<div class="title">{{item.name}}</div>
<!--作者名-->
<div class="author">
<span>{{(item.ar[0].name)}}</span>
<span class="connect">-</span>
<span>{{item.al.name}}</span>
</div>
</div>
</div>
<div class="right">
<!--播放按钮-->
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-play"></use>
</svg>
<!--更多信息-->
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-gengduo-shuxiang"></use>
</svg>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "PlayList",
props: ['playlist'],
setup(){
let changeValue = (num) =>{
let res = 0;
if(num >= 100000000){
res = (num / 100000000).toFixed(0) + '亿';
}else if(num > 10000){
res = (num / 10000).toFixed(0) + '万';
}
return res;
}
return { changeValue }
}
};
</script>
<style scoped lang="less">
.playlist{
width: 7.5rem;
padding: 0 0.4rem;
background-color: #fff;
border-top-left-radius: 0.3rem;
border-top-right-radius: 0.3rem;
.playlist-top{
display: flex;
justify-content: space-between;
align-items: center;
height: 1.1rem;
margin-top: 0.5rem;
padding-top: 0.1rem;
.left{
display: flex;
align-items: center;
.icon{
width: 0.5rem;
height: 0.5rem;
margin-right: 0.2rem;
}
.text{
display: flex;
align-items: center;
.title{
font-size: 0.35rem;
}
.num{
font-size: 0.3rem;
color: #ccc;
}
}
}
.btn{
display: flex;
align-items: center;
height: 0.6rem;
line-height: 0.6rem;
padding: 0.4rem 0.3rem;
font-size: 0.3rem;
color: rgba(255, 255, 255, 0.9);
background-color: #e84a5f;
border-radius: 0.5rem;
}
}
.list{
.playItem{
display: flex;
justify-content: space-between;
width: 100%;
.left{
display: flex;
align-items: center;
width: 4rem;
height: 1rem;
color: rgba(0, 0, 0, 0.5);
.index{
width: 0.2rem;
font-size: 0.33rem;
}
.content{
margin-left: 0.4rem;
.title{
font-size: 0.3rem;
color: rgba(0, 0, 0, 0.9);
// 防止标题溢出
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.author{
font-size: 0.2rem;
// 防止专辑名溢出
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
.connect{
font-size: 0.01rem;
}
}
}
}
.right{
display: flex;
align-items: center;
//padding-top: 0.1rem;
.icon{
margin-top: 0.1rem;
margin-right: 0.3rem;
width: 0.5rem;
height: 0.5rem;
}
}
}
}
}
</style> - src/store/index.js增加全局属性方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import { createStore } from 'vuex'
export default createStore({
state: {
// 初始化避免报错
playlist: [{
name: '岁月如歌',
id: 26608787,
al: {
id: 2532179,
name: "2013 陈奕迅 music life 精选",
pic: 109951166656425500,
picUrl: "http://p3.music.126.net/1I8ELtF6pswNRAVs4CwjfA==/109951166656425505.jpg",
pic_str: "109951166656425505",
},
ar: [{
name: '陈奕迅'
}]
}],
playCurrentIndex: 0
},
mutations: {
setPlaylist(state, value){
state.playlist = value;
},
},
getters: {
},
actions: {
},
modules: {
}
})
底部全局播放控件
音乐播放与暂停
- src/App.vue导入PlayController.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<template>
<router-view/>
<!--底部全局播放控件-->
<play-controller></play-controller>
</template>
<script>
import PlayController from "@/components/ListView/PlayController.vue";
export default {
name: 'App.vue',
components: {
PlayController
}
}
</script>
<style lang="less">
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
.icon {
width: 1rem;
height: 1rem;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
a{
text-decoration: none;
color: #3e4149;
}
</style> - src/components/ListView创建PlayController.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134<template>
<div class="playController">
<div class="left">
<!--封面-->
<img :src="playlist[playCurrentIndex].al.picUrl" alt="">
<div class="content">
<!--歌曲名-->
<div class="title">{{playlist[playCurrentIndex].name}}</div>
<div class="tips">横滑可以切换上下首</div>
</div>
</div>
<div class="right">
<!--播放按钮-->
<svg v-if="paused"
class="icon"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-16"></use>
</svg>
<!--暂停按钮-->
<svg v-else
class="icon pause"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-zanting"></use>
</svg>
<!--列表按钮-->
<svg class="icon playlistMusic" aria-hidden="true">
<use xlink:href="#icon-bofangliebiao"></use>
</svg>
</div>
<audio ref="audio"
:src="`https://music.163.com/song/media/outer/url?id=${playlist[playCurrentIndex].id}.mp3`"
>
</audio>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: "PlayController",
data(){
return{
paused: true
}
},
computed: {
...mapState(['playlist', 'playCurrentIndex'])
},
methods: {
play(){
// 点击按钮后判断播放按钮是否开启
if(this.$refs.audio.paused){
// 点击播放
this.$refs.audio.play();
// 隐藏播放按钮
this.paused = false;
}else{
// 点击暂停
this.$refs.audio.pause();
// 隐藏暂停按钮
this.paused = true;
}
}
}
};
</script>
<style scoped lang="less">
.playController{
width: 7.5rem;
height: 1.2rem;
left: 0;
bottom: 0;
background-color: #fff;
position: fixed;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #ccc;
.left{
display: flex;
padding: 0 0.4rem;
img{
width: 0.8rem;
height: 0.8rem;
margin-right: 0.2rem;
border-radius: 0.5rem;
}
.content{
.title{
font-size: 0.3rem;
color: rgba(0, 0, 0, 0.8);
// 防止标题溢出
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.tips{
margin-top: 0.05rem;
font-size: 0.1rem;
color: rgba(0, 0, 0, 0.5);
}
}
}
.right{
display: flex;
align-items: center;
.icon{
width: 0.6rem;
height: 0.6rem;
margin: 0 0.2rem;
}
.pause{
width: 0.57rem;
height: 0.57rem;
padding-right: 0.05rem;
}
.playlistMusic{
width: 0.57rem;
height: 0.57rem;
padding-right: 0.05rem;
//svg线条变细
shape-rendering: crispEdges;
}
}
}
</style>
切换歌曲
- api/index.js添加修改播放索引
1
2
3
4
5
6
7
8
9mutations: {
setPlaylist(state, value){
state.playlist = value;
},
// 修改播放索引
setPlayIndex(state, value){
state.playCurrentIndex = value;
}
}, - PlayList.vue添加setPlayIndex点击事件,识别播放第几首
1
2
3
4
5
6
7
8
9
10<div class="left" @click="setPlayIndex(i)">
...
</div>
<!--播放按钮-->
<svg class="icon" aria-hidden="true"
@click="setPlayIndex(i)"
>
<use xlink:href="#icon-play"></use>
</svg> - 预览效果
播放音乐页面
- 老师讲得非常乱,bug很多,部分地方我也懵,不细致写了,直接扔完整代码
- PlayController.vue导入PlayMusic.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171<template>
<div class="playController">
<!--点击封面歌曲名显示播放界面-->
<div class="left" @click="show=!show">
<!--封面-->
<img :src="playlist[playCurrentIndex].al.picUrl" alt="">
<div class="content">
<!--歌曲名-->
<div class="title">{{playlist[playCurrentIndex].name}}</div>
<div class="tips">横滑可以切换上下首</div>
</div>
</div>
<div class="right">
<!--播放按钮-->
<svg v-if="paused"
class="icon"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-16"></use>
</svg>
<!--暂停按钮-->
<svg v-else
class="icon pause"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-zanting"></use>
</svg>
<!--列表按钮-->
<svg class="icon playlistMusic" aria-hidden="true">
<use xlink:href="#icon-bofangliebiao"></use>
</svg>
</div>
<audio ref="audio"
:src="`https://music.163.com/song/media/outer/url?id=${playlist[playCurrentIndex].id}.mp3`"
>
</audio>
</div>
<!--播放界面,@back接收到子组件请求时进行取反,show控制是否显示,传递数据给子组件-->
<play-music
@back="show=!show"
v-show="show"
:paused="paused"
:play="play"
:playDetail="playlist[playCurrentIndex]"
>
</play-music>
</template>
<script>
import { mapState } from 'vuex'
import PlayMusic from "@/components/ListView/PlayMusic.vue";
export default {
name: "PlayController",
components: {
PlayMusic
},
data(){
return{
paused: true,
show: false,
}
},
computed: {
...mapState(['playlist', 'playCurrentIndex'])
},
methods: {
play(){
// 点击按钮后判断播放按钮是否开启
if(this.$refs.audio.paused){
// 点击播放
this.$refs.audio.play();
// 隐藏播放按钮
this.paused = false;
this.UpdateTime()
}else{
// 点击暂停
this.$refs.audio.pause();
// 隐藏暂停按钮
this.paused = true;
clearInterval(this.$store.state.id)
}
},
// 歌词更新时间
UpdateTime(){
this.$store.state.id = setInterval(() => {
this.$store.commit('setCurrentTime', this.$refs.audio.currentTime)
},1000)
}
},
mounted(){
// 调用store下的异步函数,第一个参数函数名reqLyric,第二个参数传入id
this.$store.dispatch(
'reqLyric',
{ id: this.playlist[this.playCurrentIndex].id }
)
},
updated() {
this.$store.dispatch(
'reqLyric',
{ id: this.playlist[this.playCurrentIndex].id }
)
}
};
</script>
<style scoped lang="less">
.playController{
width: 7.5rem;
height: 1.2rem;
left: 0;
bottom: 0;
background-color: #fff;
position: fixed;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #ccc;
.left{
display: flex;
padding: 0 0.4rem;
img{
width: 0.8rem;
height: 0.8rem;
margin-right: 0.2rem;
border-radius: 0.5rem;
}
.content{
.title{
font-size: 0.3rem;
color: rgba(0, 0, 0, 0.8);
// 防止标题溢出
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.tips{
margin-top: 0.05rem;
font-size: 0.1rem;
color: rgba(0, 0, 0, 0.5);
}
}
}
.right{
display: flex;
align-items: center;
.icon{
width: 0.6rem;
height: 0.6rem;
margin: 0 0.2rem;
}
.pause{
width: 0.57rem;
height: 0.57rem;
padding-right: 0.05rem;
}
.playlistMusic{
width: 0.57rem;
height: 0.57rem;
padding-right: 0.05rem;
//svg线条变细
shape-rendering: crispEdges;
}
}
}
</style> - src/components/ListView创建PlayMusic.vue组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315<template>
<div class="playMusic">
<!--动态获取背景占满屏幕-->
<div class="bg"
:style="{ backgroundImage: `url(${playDetail.al.picUrl})`}"
>
</div>
<div class="playTop">
<!--返回键,点击子组件向父组件发送back请求-->
<div class="back"
@click="$emit('back'); goCover()"
>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-zuojiantou"></use>
</svg>
</div>
<!--歌曲名和作者-->
<div class="center">
<div class="title">
<!--marquee滚动标签-->
<marquee behavior="scroll" direction="left">
{{playDetail.name}}
</marquee>
</div>
<div class="author">{{playDetail.ar[0].name}}</div>
</div>
<!--分享-->
<div class="share">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fenxiang-"></use>
</svg>
</div>
</div>
<div class="playContent"
v-show="!isLyric"
@click="goLyrics()"
>
<!--指针,点击播放获取active样式-->
<img :class="{active: !paused}"
class="needle"
src="@/assets/img/needle-ab.png" alt=""
>
<!--磁碟-->
<img class="disc" src="@/assets/img/disc-ip6.png" alt="">
<!--磁碟中间的背景图,点击播放获取rotate样式-->
<img :class="{rotate: !paused}"
class="playImg"
:src="playDetail.al.picUrl" alt=""
>
</div>
<!--歌词-->
<div class="playLyric"
v-show="isLyric"
@click="goCover()"
>
<p :class="{active: (
(currentTime * 1000) > item.pre &&
currentTime * 1000 < item.time)}"
v-for="(item, i) in $store.getters.lyricList"
:key="i"
ref="playLyric"
>
{{item.lyric}}
</p>
</div>
<div class="progress"></div>
<div class="playFooter">
<!--循环-->
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xunhuan"></use>
</svg>
<!--上一首-->
<svg class="icon" aria-hidden="true"
@click="goPlay(-1)"
>
<use xlink:href="#icon-shangyishoushangyige"></use>
</svg>
<!--播放-->
<svg v-if="paused"
class="icon play"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-bofang"></use>
</svg>
<!--暂停-->
<svg v-else
class="icon play"
aria-hidden="true"
@click="play"
>
<use xlink:href="#icon-zanting"></use>
</svg>
<!--下一首-->
<svg class="icon" aria-hidden="true"
@click="goPlay(1)"
>
<use xlink:href="#icon-xiayigexiayishou"></use>
</svg>
<!--待播列表-->
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-bofangliebiao"></use>
</svg>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "PlayMusic",
props: ['playDetail', 'paused', 'play'],
data(){
return{
isLyric: false
}
},
computed:{
...mapState(['lyric', 'currentTime', 'playlist', 'playCurrentIndex']),
},
watch:{
currentTime(newValue){
let p = document.querySelector('p.active');
if(p){
let offsetTop = p.offsetTop;
this.$refs.playLyric.scrollTop = p.offsetTop;
}
}
},
methods:{
// 切换歌曲
goPlay(num){
let index = this.playCurrentIndex + num;
if(index < 0){
index = this.playlist.length - 1;
}else if(index === this.playlist.length){
index = 0;
}
this.$store.commit('setPlayIndex', index)
},
goCover(){
this.isLyric = false;
},
goLyrics(){
this.isLyric = !this.isLyric;
}
}
};
</script>
<style scoped lang="less">
.playMusic{
position: fixed;
left: 0;
top: 0;
//宽高各100vw、100vh占满全屏
width: 100vw;
height: 100vh;
//优先级设高一点,避免主页打开显示其他内容
z-index: 1;
//添加滤镜后四周透明需要再加白色背景遮挡
background-color: #fff;
.bg{
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-size: auto 100%;
background-position: center;
// 添加滤镜使背景模糊,降低亮度
filter: blur(0.8rem) brightness(80%);
}
.playTop{
display: flex;
position: absolute;
justify-content: space-between;
align-items: center;
left: 0;
top: 0;
width: 7.5rem;
height: 1.2rem;
padding: 0.8rem 0.4rem;
color: #fff;
.icon{
width: 0.7rem;
height: 0.7rem;
fill: #fff;
}
.center{
width: 4.8rem;
height: 0.75rem;
line-height: 0.35rem;
.title{
font-size: 0.35rem;
flex: 1;
}
.author{
font-size: 0.1rem;
color: #ccc;
}
}
}
.playContent{
position: absolute;
width: 7.5rem;
height: 7.5rem;
left: 0;
top: 1.5rem;
.needle{
position: absolute;
width: 2.2rem;
height: auto;
left: 3.5rem;
//避免指针被磁碟挡住
z-index: 1;
transform-origin: 0.3rem 0;
transform: rotate(-20deg);
transition: all 1s;
}
//指针移动到磁碟上
.needle.active{
position: absolute;
width: 2.2rem;
height: auto;
left: 3.5rem;
//避免指针被磁碟挡住
z-index: 1;
transform-origin: 0.3rem 0;
transform: rotate(2deg);
transition: all 1s;
}
.disc{
position: absolute;
width: 6rem;
height: auto;
left: calc(50% - 3rem);
top: 2.2rem;
}
.playImg{
position: absolute;
width: 4rem;
height: 4rem;
border-radius: 2rem;
left: calc(50% - 2rem);
top: 3.2rem;
}
.playImg{
position: absolute;
width: 4rem;
height: 4rem;
border-radius: 2rem;
left: calc(50% - 2rem);
top: 3.2rem;
}
@keyframes changeDeg{
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
//磁碟中间背景图旋转动画
.playImg.rotate{
position: absolute;
width: 4rem;
height: 4rem;
border-radius: 2rem;
left: calc(50% - 2rem);
top: 3.2rem;
animation: changeDeg 15s linear 1s infinite;
}
}
.playFooter{
position: absolute;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 0.6rem;
padding-right: 0.6rem;
padding-bottom: 0.8rem;
width: 7.5rem;
height: 1.5rem;
left: 0;
bottom: 0;
.icon{
fill: #fff;
width: 0.6rem;
height: 0.6rem;
}
.play{
width: 1rem;
height: 1rem;
}
}
.playLyric{
position: absolute;
width: 7.5rem;
height: 8rem;
left: 0;
top: calc(50% - 4rem);
text-align: center;
padding: 0.2rem 0;
color: rgba(255, 255, 255, 0.8);
//歌词溢出滚动
overflow: scroll;
//歌词选中时变色
.active{
color: #a5dee5;
}
}
}
</style> - src/api/index.js添加获取歌词的请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import axios from 'axios';
let baseUrl = 'http://localhost:3000'
// 获取轮播图的api,type:资源类型,对应以下类型,默认为0即PC
// 1: android; 2: iphone; 3: ipad
export let getBanner = (type=0) => {
return axios.get(`${baseUrl}/banner?type=${type}`)
}
// 获取推荐歌单,可选参数limit:取出数量,默认为10
export let getMusicList = (limit=10) => {
return axios.get(`${baseUrl}/personalized?limit=${limit}`)
}
// 获取歌单详情
export let getPlaylistDetail = (id) => {
return axios.get(`${baseUrl}/playlist/detail?id=${id}`)
}
// 获取歌词
export let getLyric = (id) => {
return axios.get(`${baseUrl}/lyric?id=${id}`)
}
export default {
getBanner, getMusicList, getPlaylistDetail, getLyric
} - src/store/index.js添加播放音乐页面属性方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80import { createStore } from 'vuex'
import api from "@/api/index.js"
export default createStore({
state: {
// 初始化避免报错
playlist: [{
name: '岁月如歌',
id: 26608787,
al: {
id: 2532179,
name: "2013 陈奕迅 music life 精选",
pic: 109951166656425500,
picUrl: "http://p3.music.126.net/1I8ELtF6pswNRAVs4CwjfA==/109951166656425505.jpg",
pic_str: "109951166656425505",
},
ar: [{
name: '陈奕迅'
}]
}],
playCurrentIndex: 0,
// 歌词
lyric: '',
// 歌词更新时间
currentTime: 0,
},
mutations: {
// 修改播放列表
setPlaylist(state, value){
state.playlist = value;
},
// 修改播放索引
setPlayIndex(state, value){
state.playCurrentIndex = value;
},
// 修改歌词
setLyric(state, value){
state.lyric = value;
},
// 修改歌词更新时间
setCurrentTime(state, value){
state.currentTime = value;
}
},
actions: {
// 异步获取歌词后,调用修改歌词方法
async reqLyric(content, payload){
let result = await api.getLyric(payload.id)
content.commit('setLyric', result.data.lrc.lyric)
}
},
getters: {
// 分割歌词获取分、秒、毫、词、总时间
lyricList(state){
let arr = state.lyric.split(/\n/igs).map((item, i, arr) => {
let min = parseInt(item.slice(1, 3));
let sec = parseInt(item.slice(4, 6));
let milli = parseInt(item.slice(7, 10));
return {
min, sec, milli,
lyric:item.slice(11, item.length),
content: item,
time: milli +
sec * 1000 +
min * 60 * 1000
}
})
arr.forEach((item, i) => {
if (i===0){
item.pre = 0
}else{
item.pre = arr[i - 1].time
}
})
return arr
}
},
modules: {
}
}) - 预览效果
总结
- vue全家桶需要多练习,想要项目很好看,css美化也很重要