采用vue2+element,后台网易云接口为github某位大佬写的,上传到腾讯云服务器实现在线接口

1.代码结构

一.未登录页面

1.未登录效果预览

动图经过压缩可能渐变色不明显.....

由于图片资源过多,加载速度慢,可以采用element中图片自带的属性懒加载,再使用骨架屏后台无数据返回即显示骨架屏不至于白屏,看起来很奇怪,为啥不用数据懒加载?..好像vue2太麻烦了,没法像vue3使用现成的库,有知道的大佬望告知,但是应该可以通过页面滑动到一定高度再发请求;

2.播放预览

这部分是比较麻烦的点,由于插件功能太少只能自己封装,进度条采用elment的滑块组件,双向绑定当前歌词进度即可实现滚动,对返回的歌词进行格式化,以随歌曲进度进行滚动

音量调节也是采用element滑块,双向绑定实现调节音量功能,播放列表即为element的弹出层

通过audio的事件进行控制

<!--      audio标签--><audio :src="song.length?song[index].url:''" autoplay ref="audio"@playing="isPlay=true"@ended="this.nextSong"@play="getDuration"@timeupdate="getCurrentTime" >
</audio>

歌词格式化代码

formatLyr (lyricStr) {// 可以看network观察歌词数据是一个大字符串, 进行拆分.let reg = /\[.+?\]/g //let timeArr = lyricStr.match(reg) // 匹配所有[]字符串以及里面的一切内容, 返回数组console.log(timeArr) // ["[00:00.000]", "[00:01.000]", ......]let contentArr = lyricStr.split(/\[.+?\]/).slice(1) // 按照[]拆分歌词字符串, 返回一个数组(下标为0位置元素不要,后面的留下所以截取)console.log(contentArr)let lyricObj = {} // 保存歌词的对象, key是秒, value是显示的歌词timeArr.forEach((item, index) => {// 拆分[00:00.000]这个格式字符串, 把分钟数字取出, 转换成秒let ms = item.split(':')[0].split('')[2] * 60// 拆分[00:00.000]这个格式字符串, 把十位的秒拿出来, 如果是0, 去拿下一位数字, 否则直接用2位的值let ss = item.split(':')[1].split('.')[0].split('')[0] === '0' ? item.split(':')[1].split('.')[0].split('')[1] : item.split(':')[1].split('.')[0]// 秒数作为key, 对应歌词作为valuelyricObj[ms + Number(ss)] = contentArr[index]})return lyricObj
}

图片旋转以及订书针播放暂停切换不同效果

<div style="margin-top: 60px"><div class="needle" :style="`transform: rotate(${needleDeg});`"></div><div ref="container" :style="`animation-play-state:${isPlay ? 'running' :'paused'}`"><el-image :src="currentSong.cover" fit="cover"></el-image>
</div>
</div>

可以通过vue的动态样式通过属性控制播放或者暂停动画

3.全部歌单

这个比较简单,发请求获取数据即可,点击不同页面发请求通过offset获取新的数据提交到vuex中,实现动态数据更新

示例代码

<template><div class="back"><div class="box" style="position: relative"><HeadLine :title="title" style="margin: 0 auto" icon="iconfont icon-gedan2"></HeadLine><el-popoverplacement="bottom"title="全部分类"width="600":offset="100"trigger="click"><ul class="tags-container"><li v-for="(tag,index) in tags" :key="index" @click="getPlaylistsViaTag(tag.name)">{{ tag.name }}</li></ul><el-button type="danger" plain size="small" style="position: absolute;top: 20px;left: 220px" slot="reference">全部分类</el-button></el-popover><ul class="items" v-if="lists.playlists.length"><li v-for="item in lists.playlists" :key="item.id" @click="toListDetail(item)"><RecommendItem:listeners="item.playCount":title="item.name":image-url="item.coverImgUrl"></RecommendItem></li></ul><ul class="items" v-else><li v-for="i in 30" :key="i"><Skeleton></Skeleton></li></ul><Pagination :total="lists.total" :size="30" type="playlists" :tag="title"/></div></div>
</template><script>import { getAllPlaylistTags, getHotPlaylist, getHotPlaylistByTags, getPlaylistViaTags } from '@/api/home'
import { mapState } from 'vuex'export default {name: 'index',data () {return {title: '热门',tags: []}},computed: {...mapState('playlists', ['lists'])},methods: {//跳转到歌单详情页toListDetail (item) {this.$store.commit('recommendList/setDetail', item)this.$router.push({name: 'listDetail',params: item})},//获取对应标签歌单async getPlaylistsViaTag (tag) {const {data} = await getPlaylistViaTags(tag, 30)this.title = tagthis.$store.commit('playlists/setPlaylists',data)}},async created () {const {data} = await getPlaylistViaTags(this.title, 30)this.$store.commit('playlists/setPlaylists',data)const { data: { sub } } = await getAllPlaylistTags()this.tags = sub}
}
</script>

4.全部歌手

同样是发请求获取数据渲染页面即可,点击不同分类更新vuex中数据,实现视图更新

5.全部榜单

老套路,获取数据vuex更新歌单数据,视图随着更新

示例代码

<template><div class="back"><div class="box"><el-col :span="6"><el-card class="classify-container"><p style="text-align: center">全部榜单</p><ul><li :class="{selected:detail.id===list.id}" v-for="list in topLists.list" @click="toSelectedList(list)"><el-image style="width: 50px;height: 50px;" :src="list.coverImgUrl"></el-image><div><p class="ellipsis" style="width: 140px">{{ list.name }}</p><p>{{ list.updateFrequency }}</p></div></li></ul></el-card></el-col><el-card class="box-card"><div class="song-container"><el-image :src="detail.coverImgUrl"></el-image><div class="desc"><div><p style="font-size: 20px;margin-bottom: 10px">{{ detail.name }}</p><p style="font-size: 16px;color:#888888"><i class="el-icon-time"></i>最近更新: {{ detail.updateTime |timeFormatter}}</p></div><div><p style="width: 400px">{{detail.description}}</p></div></div></div><div style="display: flex;justify-content: space-between;border-bottom: 2px solid #f68f8f;padding: 5px"><div><span>歌曲列表 </span><span style="font-size: 12px;margin-left: 10px;color: #67676b">{{detail.trackCount}}首歌</span></div><span style="font-size: 14px">播放次数: <span style="color: #f68f8f">{{detail.playCount}}</span>次</span></div><SongList :songs="songs" :show-album="1>2"></SongList></el-card></div></div>
</template><script>
import { getSingerList } from '@/api/singer'
import { mapState } from 'vuex'
import { getAllTopLists, getHotPlaylistDetail, getPlaylistComments, getPlaylistDetail } from '@/api/home'
import dayjs from 'dayjs'export default {name: 'index',data () {return {title: '飙升榜'}},filters:{timeFormatter(time){return dayjs(time).format('MM月DD日')}},computed: {...mapState('topLists', ['topLists']),...mapState('recommendList', ['detail']),...mapState('recommendList', ['songs']),comments:{get(){return this.$store.state.recommendList.comments},set(val){}}},methods: {//榜单跳转async toSelectedList (list) {const { data: { playlist } } = await getPlaylistDetail(list.id)this.$store.commit('recommendList/setDetail', playlist)const songs = await getHotPlaylistDetail(playlist.id)this.$store.commit('recommendList/setSongs', songs)const comments = await getPlaylistComments(playlist.id, 10)this.$store.commit('recommendList/setComments', comments.data)}},async created () {const { data } = await getAllTopLists()// console.log(data)this.$store.commit('topLists/setTopLists', data)const {data:{playlist}} = await getPlaylistDetail(this.topLists.list[0].id)console.log(playlist)this.$store.commit('recommendList/setDetail',playlist)const songs = await getHotPlaylistDetail(playlist.id)this.$store.commit('recommendList/setSongs', songs)console.log('@@@', this.songs)const comments = await getPlaylistComments(playlist.id, 10)console.log('!!', comments.data)this.$store.commit('recommendList/setComments', comments.data)}
}
</script>

6,搜索实现

依旧是element弹出层,输入框内容发生变化即发请求获取数据,记得节流!

示例代码

 <div class="search"><el-popoverplacement="bottom"width="200"trigger="manual"v-model="visible"><div class="result"><ul v-if="searchResult.songs"><li><i class="iconfont icon-yinle"></i>歌曲</li><li class="ellipsis" @click="getSongUrl(song)" v-for="song in searchResult.songs" :key="song.id">{{ song.name }}-{{ song.artists[0].name }}</li></ul><ul v-if="searchResult.artists"><li><i class="iconfont icon-geshou1"></i>歌手</li><li @click="toSingerDetail(singer)" v-for="singer in searchResult.artists" :key="singer.id">{{ singer.name }}</li></ul><ul v-if="searchResult.albums"><li><i class="iconfont icon-zhuanji2"></i>专辑</li><li @click="toAlbumDetail(album)" class="ellipsis" v-for="album in searchResult.albums" :key="album.id">{{ album.name }}</li></ul><span v-if="!searchResult">暂无数据</span></div><el-inputslot="reference"@input="visible = true"placeholder="请输入内容"v-model="keyword"prefix-icon="el-icon-search"@focus="visible = true&&keyword!==''"@blur="visible=false"></el-input></el-popover></div><script>
async getResult (val) {if (this.timer) {clearTimeout(this.timer)}if (!this.keyword) returnthis.timer = setTimeout(async () => {const { data } = await getSearchResult(val)console.log(data)this.searchResult = data.result}, 500)},
</script>

7.登录

可以选择验证码或密码登录,通过element表单添加校验规则

示例代码

<template><div><el-dialog:visible="visible"ref="dialog"width="500px"@close="changeStatus"class="dialog-container"><div class="left"><el-image :src="require('../../../assets/images/login.jpg')" style="width: 200px"></el-image></div><div class="right"><el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" class="demo-dynamic"><el-form-item prop="mobile"><el-input maxlength="11" prefix-icon="el-icon-mobile-phone" placeholder="手机号"v-model.number="ruleForm.mobile"></el-input></el-form-item><el-form-item v-if="isUsePass" prop="password" style=""><el-input type="password" maxlength="16" prefix-icon="el-icon-lock" placeholder="密码" v-model="ruleForm.password"autocomplete="off"></el-input></el-form-item><el-form-item v-else prop="code" style=""><el-input type="text" maxlength="8" prefix-icon="el-icon-bell" placeholder="验证码" v-model="ruleForm.code"autocomplete="off"></el-input><el-button type="danger" plain size="small" :disabled="disabled" style="margin-left: 15px;width: 92px"@click="getCode('ruleForm')"><p ref="getCode">获取验证码</p></el-button></el-form-item><el-form-item style="margin: 0;display: flex;align-items: center;vertical-align: middle"><el-button v-if="isUsePass" @click="isUsePass=false" style="font-size: 14px;color: #FAACA8" type="text">验证码登录</el-button><el-button v-else @click="isUsePass=true"  style="font-size: 14px;color: #FAACA8" type="text">密码登录</el-button><el-checkbox style="font-size: 14px;margin-left: 80px" label="自动登录"></el-checkbox></el-form-item><el-form-item><el-button type="primary" style="width:50%;background-color: #f68f8f;" @click="submitForm('ruleForm')">登录</el-button><el-button style="width:50%" type="warning" plain @click="">注册</el-button></el-form-item></el-form></div><div slot="title" class="login-title"><span>登录</span></div></el-dialog></div>
</template><script>
import { getCode, userLogin } from '@/api/login'
import { setItem } from '@/utils/storage'
import { getUserInfo } from '@/api/user'export default {name: 'Login',props: {visible: {type: Boolean,default: false}},data () {//注意validator位置需在data中 return外const checkMobile = (rule, value, callback) => {if (!value) {return callback(new Error('手机号不能为空'))}setTimeout(() => {if (!Number.isInteger(value)) {callback(new Error('请输入数字值'))} else {if (!/^1[3456789]\d{9}$/.test(value)) {callback(new Error('请输入正确手机号!'))} else {//执行成执行回调什么也不传callback()}}}, 1000)}const validateCode = (rule, value, callback) => {if (value === '') {callback(new Error('请输入验证码'))} else {if (this.ruleForm.checkPass !== '') {this.$refs.ruleForm.validateField('checkPass')}callback()}}return {disabled: false,isUsePass:false,ruleForm: {code: '',mobile: '',password:''},rules: {code: [{validator: validateCode,trigger: 'blur'}],mobile: [{validator: checkMobile,trigger: 'blur'}],password: [{required:true,trigger: 'blur',message:'密码不能为空'},{min:6,max:16,message:'密码长度为6-16位',trigger: 'blur',}]}}},methods: {//发送验证码getCode (formName) {this.$refs[formName].validateField('mobile', (valid) => {if (!valid) {this.disabled = true//发送验证码try {getCode(this.ruleForm.mobile)this.$message({message: '发送验证码成功!',type: 'success'})} catch (err) {this.$message.error('发送失败,请重试!')}let second = 60// this.$refs.getCode.innerText=1const timer = setInterval(() => {if (!this.$refs.getCode) return clearTimeout(timer)this.$refs.getCode.innerText = second + 's'if (second === 0) {// return this.disabled=falseclearTimeout(timer)this.disabled = falsethis.$refs.getCode.innerText = '获取验证码'}second--}, 1000)// clearTimeout(timer)} else {console.log('error submit!!')return false}})},//登录submitForm (formName) {this.$refs[formName].validate((valid) => {if (valid&&this.isUsePass===false) {userLogin(this.ruleForm.mobile,'', this.ruleForm.code).then(async res => {// console.log('@@@',res)this.$store.commit('user/setCookie', res.data.cookie)this.$store.commit('user/setToken', res.data.token)const usrInfo = await getUserInfo()const combinedInfo = { ...usrInfo.data, ...res.data.profile }console.log('@@',combinedInfo)this.$store.commit('user/setUserDetail', combinedInfo)setItem('Cookies', res.data.cookie)this.$message({message: '登录成功!',type: 'success'})this.$refs.dialog.close()},err => {this.$message({message: '登陆失败请重试!',type: 'warning'})})}else if(valid&&this.isUsePass){userLogin(this.ruleForm.mobile,this.ruleForm.password).then(async res => {// console.log('@@@',res)this.$store.commit('user/setCookie', res.data.cookie)this.$store.commit('user/setToken', res.data.token)const usrInfo = await getUserInfo()const combinedInfo = { ...usrInfo.data, ...res.data.profile }console.log('@@',combinedInfo)this.$store.commit('user/setUserDetail', combinedInfo)setItem('Cookies', res.data.cookie)this.$message({message: '登录成功!',type: 'success'})this.$refs.dialog.close()},err => {this.$message({message: '登陆失败请重试!',type: 'warning'})})}else {console.log('error submit!!')return false}})},handleClose (done) {this.$confirm('确认关闭?').then(_ => {done()}).catch(_ => {})},changeStatus () {this.$emit('update:visible', false)}},}
</script>

登陆成功效果 可以显示所有功能

Vue实现web端仿网易云音乐 完成大部分功能相关推荐

  1. 小程序能用vue写么_仿网易云音乐APP的微信小程序【小程序和Vue版本】

    小程序版本: 首先是网易云的音乐接口: npm 源码获取见文章底部. 目前实现功能 用户 歌单 FM 播放 评论 MV 专辑 歌手 登录 歌曲红心,FM trash,收藏单曲至歌单 收听记录 歌单歌曲 ...

  2. 计算机毕业设计Node.js+Vue安卓仿网易云音乐客户端APP(程序+源码+LW+部署)

    该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程.欢迎交流 项目运行 环境配置: Node.js+ Vscode + Mysql5.7 + HBuilderX+Navicat11+Vue ...

  3. 计算机毕业设计Python+uniapp+安卓仿网易云音乐客户端APP(WEB+APP+LW)

    计算机毕业设计Python+uniapp+安卓仿网易云音乐客户端APP(WEB+APP+LW) 该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程 项目运行 环境配置: Pychram社区 ...

  4. 毕业设计:基于Springboot + Vue仿网易云音乐网站(一)开源

    项目背景 最近自学了springboot.vue.redis等技术,为了巩固,决定自己做个小网站玩玩,把学到的东西都使用一下,因为自己比较喜欢听音乐,去年一年网易云就听了1800个小时,然后也喜欢周杰 ...

  5. Vue仿网易云音乐播放器(一)

    项目简介 写了很多关于Vue的项目,都是一部分一部分的小模块,这次想把全部学过的关于Vue知识和模块写成一个完整的项目.都是组件化进行mvvm模式开发,实现了view和data的同步更新.仿网易云播放 ...

  6. 高仿网易云音乐(vue实战项目)

    高仿网易云音乐(Vue实战项目)

  7. 基于vue的高仿网易云音乐网站,实现大多数功能

    文章目录 前言 项目演示地址 线上地址 项目仓库 总结 前言 基于Vue + Vue-Router + Vuex + axios + elementui,ui参考网易云音乐,后端接口使用网易云音乐接口 ...

  8. 移动应用开发——uni-app框架 仿网易云音乐播放器学习心得

    目录 一.uni-app框架介绍 1.什么是 uni-app 2.为什么要选择uni-app 3.uni-app 统一规范 4.uni-app功能框架 二.开发工具与项目创建 1.开发工具 2.项目创 ...

  9. Flutter 仿网易云音乐App

    图 首页 歌曲播放和卡片切换 如正版一样,歌曲播放进度在播放/暂停 按钮的边框显示(页面下方,由黑变红) 没登录的话,一般只能听12秒 目前只做了 模块('超带感的说唱精选')的点播功能, 其他地方可 ...

最新文章

  1. docker 常用命令集合
  2. rownum 的一点儿研究
  3. mysql注释符号按键,Shell 注释
  4. 无责任Windows Azure SDK .NET开发入门(二):使用Azure AD 进行身份验证
  5. Microsoft Azure_Fabric
  6. UIImagePikerController 浅析
  7. hdu2438 三分
  8. numpy安装失败-小失误
  9. 把Hybris Commerce本地tomcat的keystore给本机的tomcat使用
  10. Elasticsearch Pipeline 详解
  11. 详解Vue八大生命周期钩子函数
  12. 用C#实现用免费smtp服务器(GMail)发邮件(转)
  13. java中menu用法_Android Menu用法全面讲解
  14. java调用支付宝接口代码介绍
  15. C++中容器的使用(二)
  16. php7 生成.so,centos8 php7 生成 openssl.so 文件执行make时报错:【已解决】
  17. 极通EWEBS 3.0抢“鲜”发版
  18. Introducing Heka
  19. vue生命周期 阿星小栈
  20. 计算机硬盘替换,如何更换笔记本电脑硬盘?

热门文章

  1. linux .so文件能解压吗,linux系统中rar解压文件安装和使用详解
  2. 【python】王者荣耀全英雄高清壁纸爬虫共467M(多线程)
  3. 常用的画流程图工具和脑图工具
  4. 安卓模拟器登录微信自动化测试最佳实践
  5. 软件测试的背景和前期准备
  6. HaaS EDU物联网项目实战:微信小程序实现云养花
  7. Dell precision 7720 移动工作站 nvidia 显卡安装说明
  8. 《计算机网络与因特网》复习纲要
  9. (8.2)利用Newton-Euler公式求解二连杆的动力学方程:
  10. SSO(Single Sign On)系列(一)--SSO简介