网易云音乐后台API服务搭建
步骤一
windows系统安装git
1
2//傻瓜式安装,可以一直next
步骤二
网易云音乐 NodeJS 版 API
下载好后,在vscode下新建一个文件夹
右击打开git bash here
输入
1
2git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git
github地址
步骤三用
vscode打开下载好的文件夹
进入此文件夹后输入
cnpm install安装依赖
完毕输入
1
2node app.js
运行完页面
步骤四
在终端中输入命令,创建一个新项目
1
2vue create musicapp
在public文件夹下创建一个js文件,js文件创建一个rem布局文件
实现自适应REM布局
准备工作
一、实现自适应REM布局
rem.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function remSize(){ //获取浏览器窗口文档显示区域的宽度,不包括滚动条。 var deviceWidth = document.documentElement.clientWidth || window.innerWidth if(deviceWidth > 750){ deviceWidth = 750 } if(deviceWidth <= 320){ deviceWidth = 320 } //设计稿是750 设置一半的宽度那么设计稿的宽度1rem等于设计稿的100像素 document.documentElement.style.fontSize = (deviceWidth / 7.5) + 'px'; document.querySelector('body').style.fontSize = 0.3 + 'rem'; } remSize() window.onresize = function(){//onreset 事件在表单被重置后触发。 remSize() }
index.html中导入rem.js
1
2<script src="<%= BASE_URL %>js/rem.js"></script>
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <!-- <%= BASE_URL %>基础路径 --> <script src="<%= BASE_URL %>js/rem.js"></script> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
字体图标
阿里巴巴矢量图图标库
加好购物车后添加至项目,可在线获取链接
引入到页面
二、头部导航布局与样式
效果如下
App.vue中添加topNav组件,设置全局字体图标样式
App.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<template> <div id="nav"> <topNav/> </div> <!-- <router-view/> --> </template> <script> import topNav from '../src/components/topNav.vue' export default { components: { topNav } } </script> <style lang="less"> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } *{ padding: 0; margin: 0; box-sizing: border-box; font-family: "微软雅黑"; } </style>
topNav.vue中设置topNav组件样式
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<template> <div> <div class="topNav"> <div class="topleft"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-caidan"></use> </svg> </div> <div class="topmain"> <span class="navBtn">我的</span> <span class="navBtn active">发现</span> <span class="navBtn">云村</span> <span class="navBtn">视频</span> </div> <div class="topright"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-sousuo"></use> </svg> </div> </div> </div> </template> <style socoped lang="less"> .topNav { display: flex; width: 7.5rem; height: 1rem; justify-content: space-between; align-items: center; padding:0 0.2rem; //lang="less" .icon{ width: 0.5rem; height: 0.5rem; } .search{ width: 0.45rem; height: 0.45rem; } } .topmain{ width: 5rem; display: flex; justify-content: space-around; .active{ font-weight: 900; } } </style>
ps:
1
2
3
4
5/* space-between 最左、最右item贴合左侧或右侧边框,item与item之间间距相等。 space-around 每个item 左右方向的margin相等。两个item中间的间距会比较大 */
三、导入轮播组件
vue3- swiper组件
1
2npm install swiper@5.4.5
1
2npm install vue-awesome-swiper@4.1.1
swiperzujian.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<template> <div> <div class="swiper-container"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="(item,i) in imgs" :key="i"><img :src="item" alt=""></div> </div> <!-- 如果需要分页器 --> <div class="swiper-pagination"></div> </div> </div> </template> <script> import Swiper from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; export default { data:function(){ return{ imgs:[ require('../assets/imag/adpage1.jpg'), require('../assets/imag/adpage2.jpg'), require('../assets/imag/adpage3.jpg'), ] } }, components: {}, mounted() { var mySwiper = new Swiper(".swiper-container", { loop: true, // 循环模式选项 // 如果需要分页器 pagination: { el: ".swiper-pagination" }, }); } }; </script> <style lang="less"> .swiper-container { width: 7.1rem; height: 3rem; border-radius: 0.1rem; } .swiper-slide img{ width: 100%; } .swiper-pagination-bullet-active{ background-color: rgb(179, 178, 177); } </style>
四、封装请求获取网易的banner图
安装axios
1
2npm install axios --save
banner
1
2/banner?type=2
启动搭建的服务器请求服务器地址加上接口地址
1
2http://localhost:3000/banner?type=2
swiperzujian.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<template> <div> <div class="swiper-container" id="d1"> <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 from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; import axios from "axios"; export default { data: function() { return { imgs: [ //因为请求数据中banner是一个对象,img存放在pc属性上 { pic: require("../assets/imag/adpage1.jpg") }, { pic: require("../assets/imag/adpage2.jpg") }, { pic: require("../assets/imag/adpage3.jpg") }, { pic: require("../assets/imag/adpage3.jpg") } ] }; }, mounted() { var mySwiper = new Swiper(".swiper-container", { loop: true, // 循环模式选项 // 如果需要分页器 pagination: { el: ".swiper-pagination", clickable: true } }); async function ff() { let res = await axios.get("http://localhost:3000/banner?type=2"); return res; } ff().then(res => { this.imgs = res.data.banners; }); } }; </script> <style lang="less"> #d1.swiper-container { width: 7.1rem; height: 2.8rem; border-radius: 0.1rem; } .swiper-slide img { width: 100%; } .swiper-pagination-bullet-active { background-color: rgb(179, 178, 177); } </style>
封装一下
src下面的文件夹api下的index.js
1
2
3
4
5
6
7
8
9
10
11
12
13import axios from 'axios'; //获取轮播图API /* 0: pc 1: android 2: iphone 3: ipad */ export async function ff(type = 2) { let res = await axios.get(`http://localhost:3000/banner?type=${type}`); return res; }
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<template> <div> <div class="swiper-container" id="d1"> <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 from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; import axios from "axios"; import {ff} from '../api/index.js' export default { data: function() { return { imgs: [ { pic: require("../assets/imag/adpage1.jpg") }, { pic: require("../assets/imag/adpage2.jpg") }, { pic: require("../assets/imag/adpage3.jpg") }, { pic: require("../assets/imag/adpage3.jpg") } ] }; }, mounted() { var mySwiper = new Swiper(".swiper-container", { loop: true, // 循环模式选项 // 如果需要分页器 pagination: { el: ".swiper-pagination", clickable: true } }); ff(2).then(res => { this.imgs = res.data.banners; }); } }; </script> <style lang="less"> #d1.swiper-container { width: 7.1rem; height: 2.8rem; border-radius: 0.1rem; } .swiper-slide img { width: 100%; } .swiper-pagination-bullet-active { background-color: rgb(179, 178, 177); } </style>
网易云音乐效果图
五、图标列表组件
新建一个模板名为iconList.vue加入到App.vue中
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<template> <div class="iconList"> <div class="iconItem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-tuijian"></use> </svg> <span>每日推荐</span> </div> <div class="iconItem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-vipsirenzhuanxiangdingzhiyewukehu"></use> </svg> <span>私人FM</span> </div> <div class="iconItem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-gedan"></use> </svg> <span>歌单</span> </div> <div class="iconItem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-paihangbang"></use> </svg> <span>排行榜</span> </div> </div> </template> <style lang="less" scoped> .iconList{ display: flex; padding: 0.34rem; justify-content: space-between; .icon{ height: 0.8rem; width: 0.8rem; } .iconItem{ display: flex; flex-direction: column; text-align: center; align-items: center; font-size: 0.28rem; } } </style>
六、发现好歌单实现
新建模板musicList.vue并引入到App.vue
1.实现基本布局
2.设计好基本布局后做ajax请求
1
2
3
4
5
6getMusicList(10).then(res => { console.log(res); this.musicList = res.data.result; });
请求结果如下图
api中index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import axios from 'axios'; //获取轮播图API /* 0: pc 1: android 2: iphone 3: ipad */ export async function ff(type = 2) { return await axios.get(`http://localhost:3000/banner?type=${type}`); } //获取推荐歌单默认十条数据 export async function getMusicList(limit = 10){ return await axios.get(`http://localhost:3000/personalized?limit=${limit}`) }
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<template> <div class="musicList"> <div class="musicList-Top"> <div class="title">发现好歌单</div> <div class="more">查看更多</div> </div> <div class="mlist"> <!-- swiper-container --> <div class="swiper-container" id="d2"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="(item,i) in musicList" :key="i"> <img :src="item.picUrl"> <div class="name">{{item.name}}</div> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{item.playCount}}</span> </div> </div> </div> </div> </div> </div> </template> <script scoped> import Swiper from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; import { getMusicList } from "@/api/index.js"; export default { data() { return { musicList: [] }; }, mounted() { var swiper = new Swiper("#d2", { slidesPerView: 3, spaceBetween: 10 }); getMusicList(10).then(res => { this.musicList = res.data.result; }); }, updated() { var swiper = new Swiper("#d2", { slidesPerView: 3, spaceBetween: 10 }); } }; </script> <style lang="less" scoped> .musicList { width: 7.5rem; padding: 0.4rem; .musicList-Top { display: flex; align-content: center; justify-content: space-between; height: 1rem; .title { font-size: 0.4rem; font-weight: 900; } .more { border: 1px solid #ccc; border-radius: 0.1rem; padding: 0.08rem; font-size: 0.24rem; text-align: center; 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; } .name { height: 0.6rem; width: 100%; font-size: 0.24rem; line-height: 0.4rem; text-align: center; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; } } } } } </style>
过滤函数(尝试把播放量数组改成亿或者万,调用方法直接在变量中引用方法)
1
2
3
4
5
6
7
8
9
10
11
12
13changeValue: function(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; }
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"> <!-- swiper-container --> <div class="swiper-container" id="d2"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="(item,i) in musicList" :key="i"> <img :src="item.picUrl"> <div class="name">{{item.name}}</div> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{changeValue(item.playCount)}}</span> </div> </div> </div> </div> </div> </div> </template> <script scoped> import Swiper from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; import { getMusicList } from "@/api/index.js"; export default { data() { return { musicList: [] }; }, mounted() { var swiper = new Swiper("#d2", { slidesPerView: 3, spaceBetween: 10 }); getMusicList(10).then(res => { this.musicList = res.data.result; }); }, updated() { var swiper = new Swiper("#d2", { slidesPerView: 3, spaceBetween: 10 }); }, methods: { //过滤函数 changeValue: function(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; } } }; </script> <style lang="less" scoped> .musicList { width: 7.5rem; padding: 0.4rem; .musicList-Top { display: flex; align-content: center; justify-content: space-between; height: 1rem; .title { font-size: 0.4rem; font-weight: 900; } .more { border: 1px solid #ccc; border-radius: 0.1rem; padding: 0.08rem; font-size: 0.24rem; text-align: center; 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; } .name { height: 0.6rem; width: 100%; font-size: 0.24rem; line-height: 0.4rem; text-align: center; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; //svg用fill设置颜色 } } } } } </style>
使用vue3实现好歌单功能
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<template> <div class="musicList"> <div class="musicList-Top"> <div class="title">发现好歌单</div> <div class="more">查看更多</div> </div> <div class="mlist"> <!-- swiper-container --> <div class="swiper-container" id="d2"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="(item,i) in musicList.musicLists" :key="i"> <img :src="item.picUrl"> <div class="name">{{item.name}}</div> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{changeValue(item.playCount)}}</span> </div> </div> </div> </div> </div> </div> </template> <script scoped> import Swiper from "swiper"; import "swiper/css/swiper.css"; import "swiper/js/swiper.min.js"; import { getMusicList } from "@/api/index.js"; import { reactive, onMounted, onUpdated } from "vue"; export default { setup() { let musicList = reactive({ musicLists: [] }); function changeValue(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; } onMounted(() => { getMusicList(10).then(res => { musicList.musicLists = res.data.result; }); }), onUpdated(() => { var swiper = new Swiper("#d2", { slidesPerView: 3, spaceBetween: 10 }); }); return { musicList, changeValue }; } }; </script> <style lang="less" scoped> .musicList { width: 7.5rem; padding: 0.4rem; .musicList-Top { display: flex; align-content: center; justify-content: space-between; height: 1rem; .title { font-size: 0.4rem; font-weight: 900; } .more { border: 1px solid #ccc; border-radius: 0.1rem; padding: 0.08rem; font-size: 0.24rem; text-align: center; 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; } .name { height: 0.6rem; width: 100%; font-size: 0.24rem; line-height: 0.4rem; text-align: center; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; //svg用fill设置颜色 } } } } } </style>
七、setup中获取路由信息
通过路由获取id值
1
2
3
4const router = useRouter() let id = router.currentRoute._value.query.id
1
2
3
4
5//获取歌单的详情 export async function getMusicContent(id){ return await axios.get(`${localhostUrl}/playlist/detail?id=${id}`) }
api 下的index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import axios from 'axios'; //获取轮播图API /* 0: pc 1: android 2: iphone 3: ipad */ let localhostUrl = 'http://localhost:3000' export async function ff(type = 2) { return await axios.get(`${localhostUrl}/banner?type=${type}`); } //获取推荐歌单默认十条数据 export async function getMusicList(limit = 10){ return await axios.get(`${localhostUrl}/personalized?limit=${limit}`) } //获取歌单的详情 export async function getMusicContent(id){ return await axios.get(`${localhostUrl}/playlist/detail?id=${id}`) }
router 下的index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/listview', name: 'listview', component: () => import('../views/listview.vue') }, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
views下的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
25<template> <div> <h1>listview</h1> </div> </template> <script> import { getMusicContent } from "@/api/index.js"; import { reactive, onMounted, onUpdated } from "vue"; import { useRouter } from "vue-router"; //reactive响应式 export default { setup() { const router = useRouter(); let id = router.currentRoute._value.query.id; let state = reactive({ list: [] }); onMounted(() => { getMusicContent(id).then(res => { }); }); } }; </script> <style lang="less" scoped> </style>
views下的home
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<template> <div class="home"> <top-nav/> <swiperzujian/> <iconList/> <musicList/> </div> </template> <script> import topNav from "@/components/topNav.vue"; import swiperzujian from "@/components/swiperzujian.vue"; import iconList from "@/components/iconList.vue"; import musicList from "@/components/musicList.vue"; export default { name: "Home", components: { topNav, swiperzujian,iconList,musicList }, data() { return {}; } }; </script> <style lang="less"> </style>
App.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<template> <div id="nav"> <router-view/><!-- router-view标签是指路由,其实就是指向的意思 --> </div> </template> <script> export default { components:{ } } </script> <style lang="less"> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } *{ padding: 0; margin: 0; box-sizing: border-box; font-family: "微软雅黑"; } a { text-decoration: none; color: #333; } </style>
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<template> <div class="listviewTop"> <img :src="playlist.coverImgUrl" class="bg"> <div class="listViewTopNav"> <div class="back"> <svg class="icon" aria-hidden="true"> <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-sousuo1"></use> </svg> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-diandiandianshu-copy"></use> </svg> </div> </div> </div> </template> <script> export default { props: ["playlist"] }; </script> <style lang="less" scoped> .listviewTop { .bg { position: fixed; left: 0; top: 0; width: 100%; height: auto; z-index: -1; filter: blur(40px); } width: 7.5rem; padding: 0 0.4rem; height: 6rem; font-size: 0.4rem; .listViewTopNav { line-height: 0.7rem; color: #fff; display: flex; justify-content: space-between; align-items: center; height: 1rem; .back, .right { display: flex; .icon { font-size: 0.6rem; color: #fff; } .search { margin-right: 0.4rem; } } .back { .icon { font-size: 0.7rem; } .title { margin-left: 0.4rem; } } } } </style>
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> <div class="listview" > <listviewTop :playlist="state.playlist"/> </div> </template> <script> import { getMusicContent } from "@/api/index.js"; import { reactive, onMounted, onUpdated } from "vue"; import { useRouter ,useRoute} from "vue-router"; import listviewTop from "@/components/listviewTop.vue" //reactive响应式 export default { setup() { const router = useRouter(); const route = useRoute(); //state是响应式对象,所以传它 let state = reactive({ list: [],playlist:{} }); onMounted(() => { let id = router.currentRoute._value.query.id; getMusicContent(id).then(res => { console.log(res) state.playlist = res.data.playlist; }); }); return{ state } }, components:{ listviewTop } }; </script> <style lang="less" scoped> </style>
八、歌单详情内容
从数据中获取
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
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<template> <div class="listviewTop"> <img :src="playlist.coverImgUrl" class="bg"> <div class="listViewTopNav"> <div class="back"> <svg class="icon" aria-hidden="true"> <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-sousuo1"></use> </svg> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-diandiandianshu-copy"></use> </svg> </div> </div> <div class="content"> <div class="contentleft"> <img :src="playlist.coverImgUrl" class="imag"> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{changeValue(playlist.playCount)}}</span> </div> </div> <div class="contentrigh"> <h4>{{playlist.name}}</h4> <div class="author"> <div class="hearder"> <img :src="playlist.creator.avatarUrl"> <span>{{playlist.creator.nickname}}</span> </div> <div class="discription">{{playlist.description}}</div> </div> </div> </div> <div class="iconList"> <div class="iconitem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-IMliaotian-duihua"></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.shareCount}}</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-checkbox"></use> </svg> <span>多选</span> </div> </div> </div> </template> <script> export default { props: ["playlist"], setup() { function changeValue(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; } return { changeValue }; } }; </script> <style lang="less" scoped> .listviewTop { .bg { position: fixed; left: 0; top: 0; width: 100%; height: auto; z-index: -1; filter: blur(40px); } overflow: hidden; width: 7.5rem; padding: 0 0.4rem; height: 6rem; font-size: 0.4rem; .listViewTopNav { padding-top: 0.2rem; line-height: 0.7rem; color: #fff; display: flex; justify-content: space-between; align-items: center; height: 1rem; .back, .right { display: flex; .icon { font-size: 0.6rem; color: #fff; } .search { margin-right: 0.4rem; } } .back { .icon { font-size: 0.7rem; } .title { margin-left: 0.4rem; } } } .content { padding-top: 0.5rem; display: flex; justify-content: space-between; .contentleft { position: relative; img { width: 3rem; height: 3rem; border-radius: 0.1rem; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; //svg用fill设置颜色 } } } .contentrigh { position: relative; h4 { color: #fff; } width: 3.3rem; display: flex; flex-direction: column; .author { flex: 1; display: flex; flex-direction: column; align-content: center; justify-content: flex-start; height: 3rem; .hearder { padding-top: 0.1rem; display: flex; flex: 1; span { padding-left: 0.15rem; font-size: 0.24rem; color: rgb(240, 240, 240); line-height: 0.7rem; } img { width: 0.6rem; height: 0.6rem; border-radius: 0.3rem; } } .discription { position: absolute; bottom: 0.1rem; font-size: 0.24rem; color: rgb(209, 209, 209); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } } } .iconList { display: flex; justify-content: space-between; padding: 0 0.3rem; padding-top: 0.3rem; align-items: center; .iconitem { display: flex; flex-direction: column; color: #fff; .icon { font-size: 0.6rem; } span { padding-top: 0.1rem; font-size: 0.24rem; line-height: 0.24rem; text-align: center; } } } } </style>
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
25
26
27
28
29
30
31
32
33
34
35
36<template> <div class="listview"> <listviewTop :playlist="state.playlist"/> </div> </template> <script> import { getMusicContent } from "@/api/index.js"; import { reactive, onMounted, onUpdated } from "vue"; import { useRouter, useRoute } from "vue-router"; import listviewTop from "@/components/listviewTop.vue"; //reactive响应式 export default { setup() { const router = useRouter(); const route = useRoute(); //state是响应式对象,所以传它 let state = reactive({ list: [], playlist: { creator: {} } }); onMounted(() => { let id = router.currentRoute._value.query.id; getMusicContent(id).then(res => { state.playlist = res.data.playlist; console.log(res); }); }); return { state }; }, components: { listviewTop } }; </script> <style lang="less" scoped> </style>
详情页的图标列表与返回页面
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<template> <div class="listviewTop"> <img :src="playlist.coverImgUrl" class="bg"> <div class="listViewTopNav"> <div class="back" @click="$router.go(-1)"> <svg class="icon" aria-hidden="true"> <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-sousuo1"></use> </svg> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-diandiandianshu-copy"></use> </svg> </div> </div> <div class="content"> <div class="contentleft"> <img :src="playlist.coverImgUrl" class="imag"> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{changeValue(playlist.playCount)}}</span> </div> </div> <div class="contentrigh"> <h4>{{playlist.name}}</h4> <div class="author"> <div class="hearder"> <img :src="playlist.creator.avatarUrl"> <span>{{playlist.creator.nickname}}</span> </div> <div class="discription">{{playlist.description}}</div> </div> </div> </div> <div class="iconList"> <div class="iconitem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-IMliaotian-duihua"></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.shareCount}}</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-checkbox"></use> </svg> <span>多选</span> </div> </div> </div> </template> <script> export default { props: ["playlist"], setup() { function changeValue(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; } return { changeValue }; } }; </script> <style lang="less" scoped> .listviewTop { .bg { position: fixed; left: 0; top: 0; width: 100%; height: auto; z-index: -1; filter: blur(40px); } overflow: hidden; width: 7.5rem; padding: 0 0.4rem; height: 6rem; font-size: 0.4rem; .listViewTopNav { padding-top: 0.2rem; line-height: 0.7rem; color: #fff; display: flex; justify-content: space-between; align-items: center; height: 1rem; .back, .right { display: flex; .icon { font-size: 0.6rem; color: #fff; } .search { margin-right: 0.4rem; } } .back { .icon { font-size: 0.7rem; } .title { margin-left: 0.4rem; } } } .content { padding-top: 0.5rem; display: flex; justify-content: space-between; .contentleft { position: relative; img { width: 3rem; height: 3rem; border-radius: 0.1rem; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; //svg用fill设置颜色 } } } .contentrigh { position: relative; h4 { color: #fff; } width: 3.3rem; display: flex; flex-direction: column; .author { flex: 1; display: flex; flex-direction: column; align-content: center; justify-content: flex-start; height: 3rem; .hearder { padding-top: 0.1rem; display: flex; flex: 1; span { padding-left: 0.15rem; font-size: 0.24rem; color: rgb(240, 240, 240); line-height: 0.7rem; } img { width: 0.6rem; height: 0.6rem; border-radius: 0.3rem; } } .discription { position: absolute; bottom: 0.1rem; font-size: 0.24rem; color: rgb(209, 209, 209); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } } } .iconList { display: flex; justify-content: space-between; padding: 0 0.3rem; padding-top: 0.3rem; align-items: center; .iconitem { display: flex; flex-direction: column; color: #fff; .icon { font-size: 0.6rem; } span { padding-top: 0.1rem; font-size: 0.24rem; line-height: 0.24rem; text-align: center; } } } } </style>
九、播放列表实现
还是基本布局以及循环拿到的数据
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<template> <div class="playlist"> <div class="playlist-Top"> <div class="left"> <svg class="icon search" aria-hidden="true"> <use xlink:href="#icon-bofang"></use> </svg> <div class="com1"> <div class="com2"> <div class="title">播放全部</div> <div class="num">(共{{playlist.tracks.length}}首)</div> </div> </div> </div> <div class="btn">+收藏({{playlist.subscribedCount}})</div> </div> <div class="list"> <div class="listitem" v-for="(item,i) in playlist.tracks" :key="i"> <div class="playCount">{{i+1}}</div> <div class="playcontent"> <div class="h4">{{item.name}}</div> <div class="author"> <span class="tag" v-for="(item,i) in playlist.tags" :key="i">{{item}}</span> <div class="discription">{{item.al.name}}</div> </div> </div> <div class="playicon"> <svg class="icon play" aria-hidden="true"> <use xlink:href="#icon-bofang2"></use> </svg> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-diandian"></use> </svg> </div> </div> </div> </div> </template> <script> export default { props: ["playlist"] }; </script> <style lang="less" scoped> .playlist { border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem; background-color: #fff; width: 7.5rem; .playlist-Top { position: relative; display: flex; height: 1.2rem; align-items: center; width: 7.5rem; justify-content: space-between; .left { width: 6.7rem; flex: 1; display: flex; font-size: 0.4rem; padding-left: 0.2rem; .icon { width: 0.5rem; height: 0.5rem; font-size: 0.5rem; } .com1 { width: 5.5rem; margin-left: 0.3rem; display: flex; font-size: 0.34rem; font-family: "微软雅黑"; color: #333; .com2 { display: flex; align-items: center; .num { line-height: 0.3rem; font-size: 0.3rem; color: rgba(187, 185, 185, 0.664); } } } } .btn { position: absolute; right: 0.15rem; font-size: 0.27rem; color: #fff; height: 0.85rem; line-height: 0.85rem; text-align: center; border-radius: 0.4rem; width: 2.4rem; background-color: #ff4935; } } .list { position: relative; width: 7.5rem; height: 1.2rem; .listitem { .playCount { height: 1.2rem; width: 1rem; text-align: center; line-height: 1.2rem; color: rgb(165, 164, 164); font-size: 0.36rem; } background-color: #fff; display: flex; position: relative; .playcontent { .h4 { padding-top: 0.1rem; display: flex; align-items: center; height: 0.85rem; font-size: 0.3rem; } .author { bottom: 0.1rem; position: absolute; height: 0.35rem; display: flex; align-items: center; span { width: 2.8em; text-align: center; height: 0.25rem; color: rgb(250, 43, 43); border-radius: 3px; font-size: 0.16rem; line-height: 0.2rem; border: 0.5px solid #ee8888; background-color: #ffd0c5a4; margin-right: 0.1rem; } .discription { color: #c2bdbd; height: 0.3rem; line-height: 0.3rem; font-size: 0.25rem; } } } .playicon { // z-index: -1; // position: fixed; position: absolute; right: 0.25rem; height: 1.2rem; line-height: 1.2rem; text-align: center; margin-top: 0.1rem; .icon { font-size: 0.5rem; } .play{ margin-right: 0.2rem; } } } } } </style>
public文件夹下的js文件夹的index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> <script src="//at.alicdn.com/t/font_3042525_nvfyrtl9iw.js"></script> </head> <body> <script src="<%= BASE_URL %>js/rem.js"></script> </noscript> <div id="app"></div> </body> </html>
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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<template> <div class="listview"> <listviewTop :playlist="state.playlist"/> <playlist :playlist="state.playlist"/> </div> </template> <script> import { getMusicContent } from "@/api/index.js"; import { reactive, onMounted, onUpdated } from "vue"; import { useRouter, useRoute } from "vue-router"; import listviewTop from "@/components/listviewTop.vue"; import playlist from "@/components/playlist.vue" //reactive响应式 export default { setup() { const router = useRouter(); const route = useRoute(); //state是响应式对象,所以传它 let state = reactive({ list: [], playlist: { creator: {},tracks:{} } }); onMounted(() => { let id = router.currentRoute._value.query.id; getMusicContent(id).then(res => { state.playlist = res.data.playlist; console.log(res); }); }); return { state }; }, components: { listviewTop,playlist } }; </script> <style lang="less" scoped> .listview{ display: flex; flex-direction: column; } </style>
listviewTop.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<template> <div class="listviewTop"> <img :src="playlist.coverImgUrl" class="bg"> <div class="listViewTopNav"> <div class="back" @click="$router.go(-1)"> <svg class="icon" aria-hidden="true"> <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-sousuo1"></use> </svg> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-diandiandianshu-copy"></use> </svg> </div> </div> <div class="content"> <div class="contentleft"> <img :src="playlist.coverImgUrl" class="imag"> <div class="count"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-whiteplayCircle"></use> </svg> <span>{{changeValue(playlist.playCount)}}</span> </div> </div> <div class="contentrigh"> <h4>{{playlist.name}}</h4> <div class="author"> <div class="hearder"> <img :src="playlist.creator.avatarUrl"> <span>{{playlist.creator.nickname}}</span> </div> <div class="discription">{{playlist.description}}</div> </div> </div> </div> <div class="iconList"> <div class="iconitem"> <svg class="icon" aria-hidden="true"> <use xlink:href="#icon-IMliaotian-duihua"></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.shareCount}}</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-checkbox"></use> </svg> <span>多选</span> </div> </div> </div> </template> <script> export default { props: ["playlist"], setup() { function changeValue(num) { let res = 0; if (num > 100000000) { res = num / 100000000; res = res.toFixed(2) + "亿"; } else if (num > 10000) { res = num / 10000; res = res.toFixed(2) + "万"; } return res; } return { changeValue }; } }; </script> <style lang="less" scoped> .listviewTop { .bg { position: fixed; left: 0; top: 0; width: 100%; height: auto; z-index: -1; filter: blur(40px); } overflow: hidden; width: 7.5rem; padding: 0 0.4rem; font-size: 0.4rem; .listViewTopNav { padding-top: 0.2rem; line-height: 0.7rem; color: #fff; display: flex; justify-content: space-between; align-items: center; height: 1rem; .back, .right { display: flex; .icon { font-size: 0.6rem; color: #fff; } .search { margin-right: 0.4rem; } } .back { .icon { font-size: 0.7rem; } .title { margin-left: 0.4rem; } } } .content { padding-top: 0.5rem; display: flex; justify-content: space-between; .contentleft { position: relative; img { width: 3rem; height: 3rem; border-radius: 0.1rem; } .count { position: absolute; right: 0.1rem; top: 0.1rem; color: rgb(253, 251, 251); font-size: 0.2rem; display: flex; align-items: center; .icon { font-size: 0.2rem; //svg用fill设置颜色 } } } .contentrigh { position: relative; h4 { color: #fff; } width: 3.3rem; display: flex; flex-direction: column; .author { flex: 1; display: flex; flex-direction: column; align-content: center; justify-content: flex-start; height: 3rem; .hearder { padding-top: 0.1rem; display: flex; flex: 1; span { padding-left: 0.15rem; font-size: 0.24rem; color: rgb(240, 240, 240); line-height: 0.7rem; } img { width: 0.6rem; height: 0.6rem; border-radius: 0.3rem; } } .discription { position: absolute; bottom: 0.1rem; font-size: 0.24rem; color: rgb(209, 209, 209); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } } } .iconList { height: 1.5rem; display: flex; justify-content: space-between; padding: 0 0.3rem; padding-top: 0.3rem; align-items: center; .iconitem { display: flex; flex-direction: column; color: #fff; .icon { font-size: 0.6rem; } span { padding-top: 0.1rem; font-size: 0.24rem; line-height: 0.24rem; text-align: center; } } } } </style>
效果图
网易云音乐移动端项目实战下一篇【分解中】
最后
以上就是单薄唇膏最近收集整理的关于网易云音乐移动端项目实战(分解上)的全部内容,更多相关网易云音乐移动端项目实战(分解上)内容请搜索靠谱客的其他文章。
发表评论 取消回复