Vue3实战—网易云音乐
效果展示
参考资料
Vant 4 - 轻量、可定制的移动端组件库 (vant-ui.github.io)
Interactive CSS Grid Generator | Layoutit Grid
Flex 布局语法教程 | 菜鸟教程 (runoob.com)
不要太屌丝,奋斗,有点精气神!!
不要把别人当傻子,也不要觉得自己很聪明,这都是缺乏存在感,野心存于胸,去做。这几年,他肯已经成熟进步,不要原地踏步,越颓废越丧。用时间去做自己的事情。你要一头扎进去。静下心来。去试试看看有没有错。
自己注意力的本质是身体的内核核心,要有不断的能量补充进去。
需求&技术栈&项目分析
1.如何开始构思一个项目?
1.需求分析
2.技术栈选择(新手会什么用什么即可)
3.项目结构设计:设计组件和路由
4.界面设计
5.开始开发
实际开发,大致看一下教程,先找到具备的【功能】;
然后看一下技术,用到了哪些
最后规划一下大致组件设计【可以使用什么画图工具啥的】
记住【一个优秀的程序员,架构时间永远比敲代码时间长, 一直敲的都是新手,合理选择技术栈】
2.网易云音乐的构思思路
组件命名
1.需求/功能分析(组件用【】表示)
一共3个页面和一个布局容器【Layout】放下面和上面的路由导航以及中间的页面
Layout
配置对应路由
推荐页
【Recommended playlist】
【The latest music】
【Song details】
【Song review】
热歌榜页
【Popular song ranking】
搜索页
【Search】
2.技术栈就用最新的那套:vant+axios+scss(super css)+vue3
3.项目结构设计
路由:
Recommendation page
Hit song list page
Search page
xxx[]——>Song details:router-link[paramas] to details useRoute
4.
3.后台接口启动
- 拿到接口地址
- 去下载该后台项目
- 配置node启动后台接口
项目结构设计
D:\课程\2024学习\前端项目\网易云音乐\01_笔记和ppt 相关笔记参考
D:\源码\OutCode\学习代码\黑马Vue\my-music
1.准备工作:创建项目,安装vant+axios+router,配置axios 【体力活】
2.创建页面【参考设计阶段命名】
3.布局页面
4.封装请求
5.获取后台数据
6.完成业务开发
0.VueStart
Vue工程搭建有很多【体力活】重复部分,因此将此划分为通用部分。
1.必备插件:scss,router,axios,pinia
pnpm i scss vue-router axios pinia
2. import xxx from xx
vueUse()
1.style.css
Vue官方做的响应式设计
1.流式宽高
2.媒体查询
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
2.router.js
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue"),
},
{
path: "/detail/:id",
name: "detail",
component: () => import("../views/SingleDetails.vue"),
props: true
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
3.pinia.js
import { createPinia } from 'pinia'
const pinia = createPinia()
import { defineStore } from 'pinia'
export const useTestStore = defineStore('testStore', {
state: () => {
return {
count: 0
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
4.vite.config.js @路径配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': '/src',
},
},
})
1.Vant组件库
1.安装 pnpm i vant
2.引入注册【一次性注册所有组件】
import Vant from 'vant';
import { createApp } from 'vue';
import 'vant/lib/index.css';
const app = createApp();
app.use(Vant);
// Lazyload 指令需要单独进行注册
app.use(vant.Lazyload);
3.使用
弹出框的函数调用
定制主题
关于样式穿透,样式使用专题。
2.封装请求
//1.封装请求
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:3000',
timeout: 10000
})
export default instance
//2.写API接口
import request from '../utils/request'
export const RecommendMusic = (params) => {
return request({
url: '/personalized',
method: 'GET',
params:params
})
}
export const NewSong = (params) => {
return request({
url: '/personalized/newsong',
method: 'GET',
params:params
})
}
//3.统一管理API
import { RecommendMusic,NewSong } from "./RecommendationPage";
import { Search,SearchHot } from "./SearchPage";
import {getMusicDetails} from './details'
export const RecommendMusicAPI = RecommendMusic
export const NewSongAPI = NewSong
export const SearchAPI = Search
export const SearchHotAPI = SearchHot
export const getMusicDetailsAPI = getMusicDetails
3.Layout
NavBar
Tabbar
易错问题:router-view来管理页面就统一用router-view管理,不要使用组件导入,不然会出现重复现象。
比如app.vue
应该这样
<script setup>
import { ref } from 'vue'
</script>
<template>
<div class="container">
<router-view></router-view>
</div>
</template>
<style lang="scss" scoped>
</style>
不要使用 <Layout></Layout>
Layout.vue
<script setup>
import { ref,watchEffect,computed} from 'vue'
import {useRoute} from 'vue-router'
const active = ref(0);
const route = useRoute();
const title = ref('首页');
const changeTitle=watchEffect(()=>{
if(active.value==0) title.value='首页';
if(active.value==1) title.value='搜索';
})
</script>
<template>
<van-nav-bar :title=title />
<RouterView />
<van-tabbar v-model="active" >
<van-tabbar-item icon="home-o" to="/recommend">推荐歌曲</van-tabbar-item>
<van-tabbar-item icon="search" to="/search">搜索</van-tabbar-item>
</van-tabbar>
</template>
<style lang="scss" scoped>
</style>
路由配置
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
name: "Layout",
component: () => import("../views/Layout.vue"),
children: [
{
path: "recommend",
name: "Recommend",
component: () => import("../views/RecommendationPage.vue")
},
{
path: "search",
name: "Search",
component: () => import("../views/SearchPage.vue")
},
]
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
RecommendationPage
1.基本样式布局
<script setup>
</script>
<template>
<p class="title">推荐歌单</p>
<van-row gutter="10">
<van-col span="8">
<van-image width="100%" height="100" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
<p class="song_name">[歌单1] 热门推荐</p>
</van-col>
<van-col span="8">
<van-image width="100%" height="100" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
</van-col>
<van-col span="8">
<van-image width="100%" height="100" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
</van-col>
</van-row>
</template>
<style lang="scss" scoped>
/* 标题 */
.title {
padding: 0.4rem 0.24rem;
margin: 0 0 0.4rem 0;
background-color: #eee;
color: #333;
font-size: 15px;
}
/* 推荐歌单 - 歌名 */
.song_name {
font-size: 0.6rem;
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;
/** 隐藏超出的内容 **/
}
</style>
2.后台数据
C:\Users\23599\Desktop\小满学习笔记\code\Vue3实战\网易云音乐本地接口
pnpm install
node app.js
http://localhost:3000/personalized?limit=6
3.封装Axios
1.request.js
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:3000',
timeout: 10000
})
export default instance
2.API-RecommendationPage.js
import request from '../utils/request'
export const RecommendMusic = () => {
return request({
url: '/personalized',
method: 'GET',
params:params
})
}
3.API-index.js
import { RecommendMusic } from "./RecommendationPage";
export const RecommendMusicAPI = RecommendMusic
4.使用&渲染
<script setup>
import { RecommendMusicAPI } from "@/API/index";
import { onMounted, ref } from 'vue';
const recommendList = ref([])
onMounted(
async () => {
const res = await RecommendMusicAPI({ limit: 6 })
console.log(res.data.result);
recommendList.value = res.data.result
})
</script>
<p class="title">推荐歌单</p>
<van-row gutter="10">
<van-col span="8" v-for="(item, index) in recommendList" :key="index">
<van-image width="100%" height="100" :src="item.picUrl" />
<p class="song_name">{{item.name}}</p>
</van-col>
</van-row>
最新歌单
单元格组件【title,right-icon】
//onMounted 里面只能放一个回调函数,放2个不行的哦!
onMounted (
async () => {
const res = await RecommendMusicAPI({ limit: 6 })
console.log('请求成功');
recommendList.value = res.data.result
}
)
定制主题样式
//深色模式
const theme = ref('none')
const Darkfn=()=>{
theme.value='dark'
}
<van-button type="primary" @click="Darkfn()">开启深色模式</van-button>
<van-config-provider :theme="theme" ></van-config-provider>
修复下面bar挡住的原因
SongItem.vue
通过router.query传递多个参数
<script setup>
import { ref, defineProps } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const props = defineProps({
item: {
type: Object,
}
})
const GoToPlayer = (id, name, artist) => {
console.log(id, name, artist)
router.push({
name: 'player',
query: { id: id, name: name, artist: artist }
})
}
</script>
<template>
<van-cell :title="item.name" :label="item.song.artists[0].name"
@click="GoToPlayer(item.id, item.name, item.song.artists[0].name)">
<template #right-icon>
<van-icon name="play-circle-o" size="1.5rem" class="icon" />
</template>
</van-cell>
</template>
<style lang="scss" scoped></style>
//使用
<SongItem v-for="(item, index) in newSongList" :key="index" :item="item"></SongItem>
Player.vue
1.接收基本数据
//接口报错问题
export const getMusicDetails= (id) => {
return request({
url: '/song/url',
method: 'get',
params :{id} //对象,且字段和后端接口参数一致
})
}
<script setup>
import { defineProps, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const id = route.query.id;
const name = route.query.name;
const artist = route.query.artist;
</script>
<template>
<h1>Player</h1>
<p>id: {{ id }}</p>
<p>name: {{ name }}</p>
<p>artist: {{ artist }}</p>
</template>
<style lang="scss" scoped></style>
2.书写样式并播放音乐
使用uniapp + Vue3 + uni.createInnerAudioContext()实现播放歌曲及歌词滚动、拖动进度条_vue音乐播放器歌词滚动-CSDN博客
Vue音乐播放器组件
音乐样式-————CV工程师
1.旋转播放
2.暂停,开始动画
//这个样式和播放得有专门的css来写
Song details:router-link[paramas] to details & useRoute
音乐播放/暂停
获取音乐并控制播放
css旋转样式,当播放时,改flag,flag动时样式触发即可
歌词
歌词效果
歌词前进效果
button
打开
下载
Search page
search input
获取input内容,和后台数据进行匹配 input.value————return getValue.渲染;
hot search
简单的调接口渲染
Search history
input.value————arr.push()———render()
搜索结果&热门搜索数据铺陈
<script setup>
import { onMounted, ref } from 'vue';
import { SearchAPI, SearchHotAPI } from '../API/index';
const inputValue = ref('');
const searchList = ref([]);
const searchHotList = ref([]);
onMounted(
async () => {
// 热门搜索接口
const searchHotResult = await SearchHotAPI();
searchHotList.value = searchHotResult.data.result.hots;
}
)
const onSearch = async () => {
console.log(inputValue.value);
if (inputValue.value) {
const searchResult = await SearchAPI({ keywords: inputValue.value, limit: 10 });
searchList.value = searchResult.data.result.songs;
console.log(searchList.value);
console.log('搜索成功');
inputValue.value = '';
}
}
const onHotSearch = (keywords) => {
inputValue.value = keywords;
onSearch();
}
</script>
<template>
<van-search v-model="inputValue" placeholder="请输入搜索关键词" shape="round" @search="onSearch" />
<div class="hotMusic">
<h4>热门搜索</h4>
<van-button style="margin-right: 10px;" v-for="item in searchHotList" type="default" size="small"
@click="onHotSearch(item.first)">{{ item.first }}</van-button>
</div>
<h4>最佳匹配</h4>
<van-cell :title="item.name" v-for="(item, index) in searchList" :key="index" :label="item.artists[0].name">
<template #right-icon>
<van-icon name="play-circle-o" size="1.5rem" class="icon" />
</template>
</van-cell>
</template>
<style lang="scss" scoped></style>
即时搜索&防抖优化【后面再写】
即使搜索,实时请求。
路由缓存
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
Comment
<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
const like = ref(0)
const onClickLeft = () => {
router.back()
}
const likeIcon = ref('good-job-o')
const onGoodClick = () => {
console.log('点赞')
like.value++
likeIcon.value = 'good-job'
}
</script>
<template>
<van-nav-bar title="歌曲评论" left-text="返回" left-arrow @click-left="onClickLeft" />
<h1>评论页面</h1>
<van-cell class="comment-item" title="name" label="2022-01-01 12:00:00">
<!-- 将图片放置在单元格左侧 -->
<template #icon>
<van-image class="avatar" round width="3rem" height="3rem"
src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
</template>
<!-- 右侧图标 -->
<template #right-icon>
<span>{{ like }} </span><van-icon :name="likeIcon" size="1.5rem" class="icon" @click="onGoodClick" />
</template>
<h5 style="color: #333; float: left; text-align: left; width: 100%;">我的评价是一般,唱的太烂了</h5>
</van-cell>
</template>
<style lang="scss" scoped>
.comment-item {
display: flex;
align-items: center;
/* 确保所有内容垂直居中对齐 */
}
.avatar {
margin-right: 1rem;
/* 图片与文本之间的间隔 */
}
.icon {
margin-left: auto;
/* 自动填充剩余空间,将图标推到最右边 */
}
</style>
布局须知
1.使用2端固定,中间自适应时,info的flex:1时要注意,父容器要设置宽度。
2.组件库改样式,多使用插槽
下拉刷新
<van-pull-refresh v-model="loading" @refresh="onRefresh">
</van-pull-refresh>