本项目是一个移动端项目,采用Vant框架,当然还有许多的移动端框架可以选择。这个就看自己或者团队了.
在这里面要感谢binaryify大佬的网易云api NeteaseCloudMusicApi
个人博客点击进入
项目运行地址点击进入
github地址点击进入
如果你觉得还行,希望你给我点个star
本项目使用的技术栈是:
- Vue 3.10.0
- animate 3.7.2
- axios
- vant
- vue-router 3.10.0
- vuex 3.10.0
- 移动端布局 flex rem
后端接口都是使用的NeteaseCloudMusicApi里面的api
整个文件的目录结构
├── public # 静态资源
│ ├── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── assets # 图片、字体等静态资源
| ├── components # 组件
| │ ├── about # 关于页面
| │ ├── Common # 全局公用组件
| │ ├── Gedan # 歌单
| │ ├── Login # 登录注册
| │ ├── Popup # 侧边栏和播放列表栏
| │ ├── Ranking # 排行榜
| │ ├── Search # 搜索
| │ ├── Singer # 歌手信息
| │ ├── User # 用户信息
| │ ├── Error.uve # 404
| │ ├── Footer.vue # 底部栏
| │ ├── Header.vue # 顶部
| │ ├── Home.js # 入口页面
| ├── App.vue # 入口页面
| ├── main.js # 入口文件 加载组件 初始化等
| ├── router.js # 路由
| ├── store.js # vuex
├── .gitignore.js # git忽略文件设置
├── babelrc.config.js # babel-loader 配置
├── package.json # package.json
├── postcss.config.js # postcss 配置
└── vue.config.js # vue-cli 配置
在多个组件内部我使用了动画 Animate.css,请自行体验
这个项目中,我把我能实现的都实现了,后续有时间可以继续增加功能.
整个项目中多次使用了播放事件,我就用provide/inject来实现多次调用,在App.vue页面中实现playaudio事件
reload和iffooter方法是别的方法,可以忽略
provide() {
return {
reload: this.reload,
iffooter: this.iffooter,
playaudio(id) {
setTimeout(() => {
let audio = document.getElementsByClassName("audio")[0];
let playicon = document.getElementById("playicon");
// 同一次点击时,根据状态是否播放
if (audio.src == this.$store.state.src) {
if (audio.paused) {
audio.play();
playicon.innerHTML = "";
this.$toast.success("开始播放");
} else {
audio.pause();
playicon.innerHTML = "";
this.$toast.fail("暂停播放");
}
} else {
audio.src = this.$store.state.src;
if (audio !== null) {
if (audio.paused) {
let playpromise = audio.play();
if (playpromise) {
playpromise
.then(() => {
setTimeout(() => {
playicon.innerHTML = "";
this.$toast.success("开始播放");
}, 2000);
})
.catch(err => {
console.log(err);
this.$notify({
type: "danger",
message: "该资源无法加载,请选择别的歌曲"
});
});
}
// 数字变成字符串
let gid = id.toString();
//把播放过的歌曲id 存入sessionStorage
//防止页面刷新后vuex里面的数据消失
if (this.$store.state.songid.length == 0) {
this.$store.state.songid = JSON.parse(
sessionStorage.getItem("songid")
);
}
//点击同一首歌,不添加
if (this.$store.state.songid.indexOf(gid) == -1) {
this.$store.state.songid.push(gid);
let songid = JSON.stringify(this.$store.state.songid);
sessionStorage.setItem("songid", songid);
}
// 保存播放的id
this.$store.state.current = gid;
let current = JSON.stringify(this.$store.state.current);
sessionStorage.setItem("current", current);
this.iffooter();
} else {
audio.pause();
playicon.innerHTML = "";
this.$toast.fail("暂停播放");
}
}
}
}, 600);
}
};
},
在App.vue里面调用,会根据用户当前播放的歌曲进行调整,点击图标可以进入歌曲详情,查看歌词
底部栏每次都显示当前播放的信息
songid为每次用户点击播放歌曲,就会把歌曲id存入到sessionStorage current为当前播放的歌曲id也存入到sessionStorage
// 获取信息
getimg() {
// 默认
if (JSON.parse(sessionStorage.getItem("songid")).length == 0) {
this.$refs.img.src =
"https://hexophoto-1259178461.cos.ap-beijing.myqcloud.com/photos/6.jpg";
this.$refs.name.innerHTML = "请播放歌曲!";
} else {
// 显示当前播放歌曲信息
this.current = JSON.parse(sessionStorage.getItem("current"));
// 获取歌曲信息
this.axios.get("/song/detail?ids=" + this.current).then(res => {
this.detail = res.data.songs[0];
this.imgurl = this.detail.al;
});
}
}
登录账号是网易云的账号
userlogin() {
if (this.phone == "" || this.password == "") {
this.$dialog.alert({
message: "手机号或者密码不能为空!"
});
} else {
this.axios
.get(
"/login/cellphone?phone=" +
this.phone +
"&password=" +
this.password
)
.then(res => {
this.$store.state.uid = res.data.account.id;
// 存入sessionStorage 防止刷新数据消失
let id = JSON.stringify(res.data.account.id);
sessionStorage.setItem("uid", id);
this.$dialog
.alert({
message: "登录成功!"
})
.then(() => {
this.$router.push("/sheet");
});
});
}
},
注册使用了验证码,在注册前要判断验证码是否正确,其次对于已经注册过账号的手机号,直接跳到login页面
歌单的详情基本都是一样的,我使用了一个组件来完成 Detail.vue,该页面主要展示歌单里面的歌曲,点击可以播放,收藏歌单在登录用户后,点击用户信息可以看到。
我使用了三个组件 sheet.vue ranking.vue singer.vue 来组成首页的三个部分,歌手榜用的是热门歌手数据。所以有的歌手看不到请见谅!歌手详情有五个子页面。当时做的时候本来四个就好了,结果不知道什么原因,展示的第一个页面,在从父页面获取id后,能够打印出来,但是在发送请求axios时,id就变成undefined了,我没有找到其他好的解决方法,就多加了个无关紧要的页面解决这个BUG.
因为把audio放在了app.vue上面,我在歌词页面使用了vuex来就行管理.
在用ref来获取audio时,发现获取不了,就直接用原生的JS来获取了,许多位置我都是用原生来获取的。
export default new Vuex.Store({
state: {
// 判断底部播放是否加载
footer: true,
showfooter: true,
// 登录信息
uid: 0,
// 歌曲id
songid: [],
// 歌曲资源
src: "1",
audio: {
//当前播放时间
currentTime: 0,
// 歌曲总长
maxTime: 0
},
// 进度条
playtime: 0,
// 当前播放音乐ID
current: ""
}
}
在App.vue页面
<template>
<div id="app">
<audio
class="audio"
ref="audio"
@timeupdate="onTimeupdate"
@loadedmetadata="onLoadedmetadata"
></audio>
</div>
</template>
export default {
// 记录播放时间
onTimeupdate(res) {
// 获取当前播放时间
this.$store.state.audio.currentTime = res.target.currentTime;
// 获取进度条进度
this.$store.state.playtime = parseInt(
(this.$store.state.audio.currentTime /
this.$store.state.audio.maxTime) *
100
);
},
// 获取歌曲总时长
onLoadedmetadata(res) {
this.$store.state.audio.maxTime = parseInt(res.target.duration);
},
}
点击进度条事件
click(res) {
let audio = document.getElementsByClassName("audio")[0];
// 当前点击距离
let clicktime = res.clientX;
// 进度条距离
this.playtime = parseInt((clicktime / 375) * 100);
// 更新播放时间
audio.currentTime = parseInt(
(this.playtime * this.$store.state.audio.maxTime) / 100
);
this.setroll();
},
点击搜索进入热门搜索,会根据当时的热度进行排行,再次点击会显示搜索结果,可以直接进行播放.
在两个星期内做完了这个感觉还是比较有收获的,之前对audio一点了解都没有,现在不会像之前那样一窍不通了,在各种组件传值,调用方法方面也有了不少的进步,对vue也有了进一步的了解,希望在以后能够继续加油。