网易云音乐项目开发笔记

准备

项目描述

  1. 此项目为一个前后台分离的后台管理的SPA,包括前端PC应用和后端应用
  2. 包括发现音乐、我的音乐、朋友、商城等功能模块
  3. 前端:使用Vue + Antd + Axios + ES6 + Webpack 等技术
  4. 后端:使用Node + Expressdeng 等技术
  5. 采用模块化、组件化、工程化的模式开发

项目功能页面

技术选型

前端路由

API/接口

npm/yarn常用命令

  • yarn命令文档:https://yarnpkg.com/zh-Hans/docs/cli/
  • npm命令文档:https://docs.npmjs.com/cli-documentation/

设置淘宝镜像

  • npm config set registry https://registry.npm.taobao.org
  • yarn config set registry https://registry.npm.taobao.org

git常用命令

  • 详细地址 - http://gitref.justjavac.com/

项目开发

网易云API

https://github.com/Binaryify/NeteaseCloudMusicApi

初始化项目组

使用脚手架初始化项目

编码测试

功能需求分析

项目基本目录设计

安装依赖


代码编写

api目录下

编写ajax.js

  • 发送异步请求的模块

    • 统一处理请求异常
    • 异步直接得到data
import axios from 'axios'
axios.defaults.withCredentials = true
export default function ajax (url, data = {}, method = 'GET') {return new Promise((resolve, reject) => {let promise// 1. 执行异步请求if (method === 'GET') {promise = axios.get(url, {params: data})} else {promise = axios.post(url, data)}promise.then(response => {// 2. 成功调用resolveresolve(response.data)}).catch(error => {console.log(error.message)// 3. 失败不调用reject,而是提示异常信息// this.$Message.error('请求出错了:' + error.message)})})
};

index.js

  • 包含应用中所有接口请求的函数的模块

    • 每个函数的返回值都是Promise
import ajax from './ajax'const BASE = 'http://localhost:3000'
const date = new Date()
// 登录
export const reqLoginByPhone = (userLogin) => ajax(BASE + '/login/cellphone?timestamp=' + date.getTime(), userLogin, 'POST')
// 获取登录状态
export const reqLoginStatus = () => ajax(BASE + '/login/status?timestamp=' + date.getTime())
// 退出登录
export const reqLogout = () => ajax(BASE + '/logout?timestamp=' + date.getTime())// banner图
export const reqBanner = () => ajax(BASE + '/banner', 'post')// 推荐歌单
export const reqPersonalized = () => ajax(BASE + '/personalized?limit=10', 'post')
// 每日推荐歌单
export const reqDailyMusic = () => ajax(BASE + '/recommend/resource', 'post')// 获得歌单详情
export const reqMusicListDetail = (id, time) => ajax(BASE + '/playlist/detail?timestamp=' + time, { id }, 'post')
// 获得歌曲详情
export const reqMusicDetail = (ids, time) => ajax(BASE + '/song/detail?timestamp=' + time, { ids }, 'post')
// 获得歌曲播放地址
export const reqMusicUrl = (id, time) => ajax(BASE + '/song/url?timestamp=' + time, { id }, 'post')
// 获得歌曲的歌词
export const reqGetLyric = (id) => ajax(BASE + '/lyric', { id }, 'post')// 我的云盘信息
export const reqCloud = () => ajax(BASE + '/user/cloud', 'post')

登录注册以及用户信息

compontents/header.vue

<template><Header><h1><a href="/"><img :src="img_src" alt="" /></a></h1><Buttonshape="circle"icon="ios-arrow-back"type="error"@click="go(-1)"></Button><Buttonshape="circle"icon="ios-arrow-forward"type="error"@click="go(1)"></Button><div style="width: 190px"><Input search placeholder="Enter something..." /></div><divstyle="display: flex;align-items: center;justify-content: center;width: 1024px;"class="user"v-if="user.userId !== 0 && user.userId !== undefined"><Poptip title="Title" content="content"><Avatar :src="user.avatarUrl" /><span style="color: white">{{ user.nickname }}</span><Icon type="ios-arrow-down" color="white" /><div slot="title" class="title"><div class="info"><p class="number">2</p><p>动态</p></div><div class="info"><p class="number">2</p><p>关注</p></div><div class="info"><p class="number">2</p><p>粉丝</p></div><div class="btn"><Button>Default</Button></div></div><div slot="content" class="content"><List :split="false"><ListItem><Icon type="md-star" /><span style="width: 100%">我的会员</span><Icon type="ios-arrow-forward" class="right" /></ListItem><ListItem><Icon type="md-star" /><span style="width: 100%">等级</span><Icon type="ios-arrow-forward" class="right"/></ListItem><ListItem><Icon type="md-star" /><span style="width: 100%">商城</span><Icon type="ios-arrow-forward" class="right" /></ListItem><ListItem><Icon type="md-star" /><span style="width: 100%">个人信息设置</span><Icon type="ios-arrow-forward" class="right" /></ListItem><ListItem><Icon type="md-star" /><span style="width: 100%">绑定社交账号</span><Icon type="ios-arrow-forward" class="right" /></ListItem><ListItem><Icon type="md-power" /><span style="width: 100%" @click="userLogout">退出登录</span><Icon type="ios-arrow-forward" class="right" /></ListItem></List></div></Poptip></div><divv-elsestyle="display: flex;align-items: center;justify-content: center;margin-left:1300px;font-size:15px;"><span style="color: white" @click="userLoginModal">登录按钮</span></div><Modaltitle="用户登录"v-model="login"class-name="vertical-center-modal":mask-closable="false"@on-ok="ok"><Form ref="loginForm" :rules="userFormRule" :model="userLogin"><FormItem label="手机号码" prop="phone"><Input v-model="userLogin.phone" placeholder="Enter your phone" /></FormItem><FormItem label="密码" prop="password"><Inputv-model="userLogin.password"placeholder="Enter your password"type="password"/></FormItem><!--重点就是下面的代码了--></Form><div slot="footer"><Button type="text" size="large" @click="login = false">取消</Button><Button type="primary" size="large" @click="ok">确定</Button></div></Modal></Header>
</template><script>
import logo from '../assets/image/logo.png'
import { reqLoginByPhone, reqLoginStatus, reqLogout } from '../api'export default {data () {return {userLogin: {phone: '18630257257',password: '5296306...'},// 表单验证规则userFormRule: {phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' }]},img_src: logo,login: false,user: {avatarUrl: '',birthday: '',djStatus: '',nickname: '',userId: 0,userType: ''}}},async created () {this.getUser()},methods: {// 初始化用户信息initUser (user) {if (user.userId) {this.user = user}},userLoginModal () {this.login = !this.login},async getUser () {const s = await reqLoginStatus()if (s.code === 200) {this.$store.commit('setUser', s.profile)this.initUser(this.$store.state.user)} else {console.log(1)}},ok () {const { loginForm } = this.$refsloginForm.validate(async result => {if (result) {// 表单验证通过const result = await reqLoginByPhone(this.userLogin)if (result.code === 200) {this.getUser()this.login = false} else {this.$Message.error(result.msg)}} else {this.$Message.error('请完成验证信息')}})},// 退出登录async userLogout () {const result = await reqLogout()if (result.code === 200) {this.$Message.success('退出成功!')this.$store.commit('clearUser')this.user = {}}},go (value) {this.$router.go(value)}}
}
</script><style lang='scss' scoped>
$height: 72px;
.ivu-layout {height: 100%;header {display: flex;align-items: center;background: var(--theme_color);height: $height;}h1 {margin-left: 20px;height: $height;a {height: $height;float: left;}}.vertical-center-modal {display: flex;align-items: center;justify-content: center;.ivu-modal {top: 0;}}.user {.title {display: flex;justify-content: center;align-items: center;flex-wrap: wrap;.info {width: 33.33%;text-align: center;.number {font-size: 21px;font-weight: bold;color: black;}}}.btn {width: 100%;text-align: center;}.content {.right {&::before {position: relative;right: 0;}}}}
}.ivu-poptip-inner {.ivu-poptip-body {padding: 0 !important;.ivu-list-item {padding: 8px 0;&:hover {background-color: rgb(240, 241, 243);cursor: pointer;}}.content {margin-bottom: 0;}}
}
</style>

个性推荐以及歌单显示

src/views/home/discover/personal.vue

<template><div class="personal"><Carousel v-model="BannerIndex" loop style="text-align: center"><CarouselItem v-for="(item, index) in bannerList" :key="index"><div class="demo-carousel"><img :src="item.imageUrl" /></div></CarouselItem></Carousel><Card :bordered="false"><h2 slot="title">推荐歌单</h2><Row :gutter="20"><i-colstyle="float: left; width: 20%; height: 240px; margin-bottom: 20px"v-for="(item, index) in personalized":key="index"><divstyle="border: 1px solid;height: 100%;overflow: hidden;text-align: center;display: flex;justify-content: center;cursor: pointer;"@click="toMusicList(item)"><img :src="item.picUrl" /></div><span>{{ item.name }}</span></i-col></Row></Card></div>
</template><script>
import { reqPersonalized, reqLoginStatus, reqDailyMusic } from '../../../api'
import { mapGetters } from 'vuex'
export default {async created () {this.getDailyMusic()const result = await reqPersonalized()if (result.code === 200) {this.personalized = result.result}},methods: {async getDailyMusic () {const { code } = await reqLoginStatus()if (code === 200) {const result = await reqDailyMusic()if (result.code === 200) {this.personalized = result.recommend.splice(0, 10)// this.personalized = result.recommendconsole.log(result.recommend)} else {this.$Message.error('歌单获取失败')}} else {const result = await reqPersonalized()if (result.code === 200) {this.personalized = result.result} else {this.$Message.error('歌单获取失败')}}},toMusicList (item) {this.$router.push({ name: 'musicList', params: { id: item.id } })}},data () {return {BannerIndex: 0,bannerList: [],// 推荐歌单列表personalized: []}},computed: {...mapGetters({ getUser: 'getUser' })}}
</script><style lang='scss'>
.personal {.song {width: 200px;height: 200px;border: 1px solid black;}
}
</style>

src/compontents/player.vue

<template><div class="player"><audio:src="music.url"ref="audio"@pause="onPause"@play="onPlay"@timeupdate="onTimeupdate"@loadedmetadata="onLoadedmetadata"></audio><div class="left_party"><img :src="musicObj.al ? musicObj.al.picUrl : ''" alt="" /><div class="info"><p>{{ musicObj.name }}</p><p>{{ musicObj.al ? musicObj.al.name : '' }}</p></div></div><div class="center_party"><div class="progress"><Icontype="md-arrow-round-back":size="music.iconSize"@click="prevMusic"/><Icon:type="music.play ? 'md-pause' : 'md-play'":size="music.iconSize"@click="musicPlay"/><Icontype="md-arrow-round-forward":size="music.iconSize"@click="nextMusic"/><Sliderv-model="music.currentTime":max="music.duration":marks="music.time":tip-format="formatSecond"@on-change="changeCurrentTime"></Slider></div></div><div class="right_party" style="margin-right: 100px"><div><span><Icon type="md-volume-up" :size="music.iconSize" /></span></div><div style="width: 100px"><Sliderv-model="music.voice":step="0.1":tip-format="music.hideFormat":min="0":max="1"@on-input="changeVoice"></Slider></div><div><Icontype="ios-list"style="margin-left: 50px":size="music.iconSize"@click="onShowList"/></div></div><Drawer title="我的歌单" :closable="false" v-model="showList"><i-table:columns="columns":data="musicList"ellipsisheight="600"@on-row-dblclick="changeIndex"><template slot-scope="{ row }" slot="singal">{{ row.ar[0].name }}</template><template slot-scope="{ row }" slot="album">{{ row.al.name }}</template></i-table></Drawer></div>
</template><script>
import { reqMusicUrl } from '../api'
// 将整数转换成 时:分:秒的格式
function realFormatSecond (second) {var secondType = typeof secondif (secondType === 'number' || secondType === 'string') {second = parseInt(second)var hours = Math.floor(second / 3600)second = second - hours * 3600var mimute = Math.floor(second / 60)second = second - mimute * 60return hours + ':' + ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)} else {return '0:00:00'}
}
export default {data () {return {columns: [{title: 'Name',key: 'name'},{title: '歌手',key: 'singal',slot: 'singal'}],music: {name: '',url: '',iconSize: 30,// 歌曲时间time: {},// 音量voice: 1,// 是否播放play: false,// 音频总时间duration: 0,// 当前播放的长度currentTime: 0,// 是否暂停pause: false},// 当前音乐索引index: -1,// 显示播放列表showList: false}},created () {},computed: {musicObj () {if (this.$store.state.music.length !== 0) {return this.$store.state.music[this.index]} else {return {}}},musicList () {return this.$store.state.music}},watch: {async musicObj (newMusic, oldMusic) {if (newMusic === undefined) {this.index = 0}},index: async function (newIndex, oldIndex) {// 将索引保存在VUEX中this.$store.commit('setIndex', newIndex)this.music.url = ''const date = new Date()const result = await reqMusicUrl(this.musicObj.id, date.getTime())console.log(result)// 将请求结果保存在vuex中this.$store.commit('setMusicObj', result)this.music.play = falseconst { url } = result.data[0]this.music.url = url}},filters: {// 将整数转化成时分秒},methods: {// 隐藏滑块的提示hideFormat () {return null},formatSecond (second = 0) {return realFormatSecond(second)},// 显示歌单列表onShowList () {this.showList = !this.showList},changeVoice (voice) {this.music.voice = voicethis.$refs.audio.volume = this.music.voice},// 拖动播放进度条changeCurrentTime (index) {this.$refs.audio.currentTime = parseInt(index)},// 播放音乐musicPlay () {if (this.music.play === false) {this.$refs.audio.play()} else {this.$refs.audio.pause()}},nextMusic () {// console.log('nextMusic')this.index++},prevMusic () {this.index--console.log(this.index)},// 当音频播放onPlay () {this.music.play = true},// 当音频暂停onPause () {this.music.play = false},// 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间onTimeupdate (res) {this.music.currentTime = res.target.currentTimethis.music.time[0] = realFormatSecond(res.target.currentTime)},// 当加载语音流元数据完成后,会触发该事件的回调函数// 语音元数据主要是语音的长度之类的数据onLoadedmetadata (res) {this.music.duration = parseInt(res.target.duration)const end = parseInt(res.target.duration)const time = {0: '0:00'}time[end] = realFormatSecond(parseInt(res.target.duration))this.music.time = time},// 歌单双击改变当前播放的音乐changeIndex (item, index) {this.index = index}}
}
</script><style lang='scss'>
.ivu-drawer-body {padding: 0 !important;
}
.player {height: 80px;display: flex;justify-content: space-between;img {width: 60px;border-radius: 2px;margin-right: 10px;}.left_party {align-items: center;display: flex;width: 154px;.info {display: inline-block;overflow: hidden; //超出部分隐藏}p {overflow: hidden; //超出部分隐藏text-overflow: ellipsis; //超出部分显示...white-space: nowrap; //文本强制一行显示}}.right_party {display: flex;align-items: center;.list {width: 250px;height: 300px;position: absolute;top: -280px;right: 0;border: 1px solid;overflow-y: auto;background: white;}}.progress {justify-content: center;margin: 0 auto;text-align: center;width: 800px;.ivu-progress-bg {height: 5px !important;}}
}
</style>

src/views/home/music.vue

<template><!-- 歌单页面 --><div class="playlist-info"><div class="playlist-top"><img:src="playListInfo.coverImgUrl"style="width: 230px; height: 230px"/><div class="playlist-top-right"><Tag color="rgb(236, 65, 65)" style="margin-top:-10px" type="border">歌单</Tag><h2>{{ playListInfo.name }}</h2><div class="playlist-top-right-user"><Avatar :src="playListInfo.creator.avatarUrl" /><span style="margin-left: 10px; line-height: 30px; height: 32px">{{playListInfo.creator.nickname}}</span></div><div class="playlist-top-right-btn"><Buttonsize="large"icon="md-play"shape="circle"type="error"@click="playAll">播放全部</Button><Button size="large" icon="md-play" shape="circle">收藏</Button><Button size="large" icon="md-play" shape="circle">分享</Button><Button size="large" icon="md-play" shape="circle">下载全部</Button></div><p>标签:{{ playListInfo.tags | formatTags }}</p><p>歌曲:{{ playListInfo.trackCount }} 播放:{{ playListInfo.playCount }}</p><p>简介:{{ playListInfo.description }}</p></div></div><i-table :columns="columns" :data="musicList" ellipsis height="600"><template slot-scope="{ row }" slot="singal">{{ row.ar[0].name }}</template><template slot-scope="{ row }" slot="album">{{ row.al.name }}</template></i-table></div>
</template><script>import { reqMusicListDetail, reqMusicDetail } from '../../api'
export default {created () {this.initList()},data () {return {columns: [{ type: 'index', width: 75 },{title: 'Name',key: 'name',width: 350},{title: '歌手',key: 'singal',slot: 'singal',width: 100},{title: '专辑',key: 'album',slot: 'album'}],musicList: [],playListInfo: {coverImgUrl: '',name: '',creator: {nickname: '',avatarUrl: '',userId: 0},tags: [],trackCount: 0,playCount: 0,description: ''}}},filters: {formatTags: function (value) {if (value.length === 0) {return ''} else if (value.length === 1) {return value.pop()} else {let tags = value[0] + ''for (let i = 1; i < value.length; i++) {tags = tags + ' / ' + value[i]}return tags}}},methods: {// 初始化列表async initList () {const listId = this.$route.params.idconst date = new Date()const result = await reqMusicListDetail(listId, date.getTime()) // 获取歌单详情this.initListInfo(result.playlist)// 返回的歌单实体不完整,但ID是完整的,因此需要利用id请求歌曲获得歌曲详情const trackIds = result.playlist.trackIdsconst ids = []for (const item of trackIds) {ids.push(item.id)}this.$Spin.show()const musicLists = await reqMusicDetail(ids.toString(), date.getTime())this.$Spin.hide()if (musicLists.code !== 200) return this.$Message.error('遇到错误,我们会尽快处理!')this.musicList = musicLists.songs},initListInfo (playlist) {console.log('playList:', playlist)this.playListInfo.coverImgUrl = playlist.coverImgUrlthis.playListInfo.name = playlist.namethis.playListInfo.tags = playlist.tagsthis.playListInfo.trackCount = playlist.trackCountthis.playListInfo.playCount = playlist.playCountthis.playListInfo.description = playlist.descriptionthis.playListInfo.creator.nickname = playlist.creator.nicknamethis.playListInfo.creator.avatarUrl = playlist.creator.avatarUrl},// 播放全部playAll () {// 将列表内的音乐加入到state中this.$store.commit('setMusic', this.musicList)console.log(this.musicList)}}
}
</script><style lang='scss'>
.playlist-info {.playlist-top {padding: 20px;display: flex;align-items: space-between;// justify-content: center;.playlist-top-right {h2 {display: inline;font-size: 30px;font-weight: bold;}margin-left: 20px;.playlist-top-right-btn {// display: flex;// justify-content: space-around;margin-right: 20px;button {// margin-right: 20px;margin: 20px;}}}}
}
</style>

发表评论功能

获取接口数据

src/api/index.js

// 获取歌单的评论
export const reqGetListComment = (obj, time) => ajax(BASE + '/comment/playlist?timestamp=' + time, obj, 'post')
// 获取歌单的收藏者
export const reqGetListSubscribers = (obj, time) => ajax(BASE + '/playlist/subscribers?timestamp=' + time, obj, 'post')// 获取歌单评论点赞
export const reqCommentLike = (obj) => ajax(BASE + '/comment/like', obj, 'post')
// 发表评论
export const reqSendComment = (obj, time) => ajax(BASE + '/comment?timestamp=', obj, 'post')

src/views/home/music.vue

      <TabPane label="评论" style="padding: 20px"><Inputsize="large"type="textarea"v-model="comment"placeholder="请输入评论。。。":maxlength="140"show-word-limit/><Button shape="circle" style="text-aligin: right" @click="sendComment">Circle</Button><div class="comments"><h3>最新评论({{ total }})</h3><divclass="comment"v-for="item in commentList":key="item.commentId"><div class="user_img"><img :src="item.user.avatarUrl" /></div><div class="user_text"><a href="http://www.baidu.com" target="_blank">{{ item.user.nickname }}:</a><span>{{ item.content }}</span><!-- <p class="reply">哈哈哈</p> --><p><span><Time :time="item.time" type="datetime" /></span><span style="float: right"><span><a @click="listCommentLike(item)"><Icon:type="item.liked ? 'ios-thumbs-up' : 'ios-thumbs-up-outline'"/>({{ item.likedCount }}) </a>| <a><Icon type="ios-text-outline" /></a> |<a @click="delComment(item.commentId)"><Icon type="ios-share-alt-outline"/></a></span></span></p></div></div><Page:total="total"@on-change="pageChange":current.sync="page":styles="{ textAlign: 'center' }"/></div></TabPane>
 // 歌单评论喜欢async listCommentLike (item) {const { commentId: cid, liked } = itemconst id = this.playListInfo.idconst t = liked ? 0 : 1const type = 2const result = await reqCommentLike({ id, cid, t, type })console.log(result)},// 发表评论async sendComment () {const date = new Date()const t = 1const type = 2const id = this.playListInfo.idconst content = this.commentconst result = await reqSendComment({ t, type, id, content }, date.getTime())if (result.code === 200) {this.$Message.success('发表成功!')this.comment = ''this.getComment((this.page - 1) * 20)}},async delComment (commentId) {const date = new Date()const t = 0const type = 2const id = this.playListInfo.idconst result = await reqSendComment({ t, type, id, commentId }, date.getTime())console.log(result)if (result.code === 200) {this.$Message.success('删除成功!')this.getComment((this.page - 1) * 20)}},// 翻页pageChange (page) {console.log('翻页了!', page)this.getComment((page - 1) * 20)}}


收藏功能

<TabPane label="收藏者"><div class="collectors"><divclass="collector"v-for="item in collectorList":key="item.userId"><div class="image"><img:src="item.avatarUrl"style="width: 80px; border-radius: 50%"/></div><div class="text"><p>{{ item.nickname}}<Icon:type="item.gender == 1 ? 'ios-male' : 'ios-female'":color="item.gender == 1? 'rgb(38, 166, 228)': 'rgb(255, 181, 211)'"size="20"style="font-weight: bold"/></p><p>{{ item.signature }}</p></div></div></div></TabPane>
async getCollector (offset) {const id = this.$route.params.idconst result = await reqGetListSubscribers({ id, offset, limit: 300 })this.collectorTotal = result.totalthis.collectorList = result.subscribersconsole.log(result)},

歌曲播放详情页面

src/views/home/music-page.vue

<template><!-- 歌曲播放详情页面 --><div class="container"><div class="left"><img :src="music.al.picUrl" /></div><div class="right"><h2>{{ music.al.name }}</h2>{{ currentTime }}<p><span>专辑:{{ music.al.name }} </span><span>歌手:<span v-for="(item, index) in music.ar" :key="index">{{ item.name }}</span></span></p><div class="lyric"><div class="lyric-container" :style="{ marginTop: marginTop + 'px' }"><pv-for="(item, index) in lyrics":key="index":style="{ color: currentindex == index ? 'red' : 'black' }">{{ item }}</p></div></div></div></div>
</template><script>
import { mapState } from 'vuex'import { reqGetLyric } from '../../api'
function realFormatSecond (second) {var secondType = typeof secondif (secondType === 'number' || secondType === 'string') {second = parseInt(second)var hours = Math.floor(second / 3600)second = second - hours * 3600var mimute = Math.floor(second / 60)second = second - mimute * 60return hours + ':' + ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)} else {return '0:00:00'}
}
export default {data () {return {lyric: [],// currentTime: 0// 保存匹配的时间times: [],// 保存匹配的歌词lyrics: [],marginTop: 0,currentindex: 0}},methods: {async getLyc () {this.marginTop = 0this.lyrics = []this.times = []const date = new Date()const id = this.music.idconst result = await reqGetLyric(id, date.getTime())const array = result.lrc.lyric.split('\n')// console.log(this.lyric)// 正则表达式匹配内容const timeReg = /\[(\d*:\d*\.\d*)\]/array.forEach((ele, index) => {//   处理歌词var lrc = ele.split(']')[1]// console.log(lrc)//   排除空字符串(没有歌词)if (lrc === undefined || lrc.length === 1) return truethis.lyrics.push(lrc)var res = timeReg.exec(ele)if (res == null) return truevar timeStr = res[1]var res2 = timeStr.split(':')var min = parseInt(res2[0]) * 60var sec = parseFloat(res2[1])var time = parseFloat(Number(min + sec).toFixed(2))time = realFormatSecond(time)this.times.push(time)})// console.log(this.lyrics)},test () {// console.log(this.lyric, 1)// console.log(this.audio.currentTime)},currentIndex (time) {return this.times.indexOf(time)}},created () {this.getLyc()},computed: mapState({index: 'index',music: (state) => {const music = state.music[state.index]// this.getLyc()return music},audio: function () {// this.getLyc()return this.$route.params.audio},currentTime: function (state) {const time = realFormatSecond(state.musicTime)var index = this.currentIndex(time)if (index !== -1) {this.currentindex = indexif (index <= 5) returnthis.marginTop = (-index + 2) * 80}}}),watch: {index (i) {console.log(i)this.getLyc()}}}
</script><style lang='scss'>
.container {display: flex;width: 100%;height: 100%;.left {width: 50%;text-align: center;padding: 56px;overflow: hidden;}.right {width: 50%;// background: #fff;padding: 30px;text-align: center;p {line-height: 80px;}h2 {font-weight: bold;font-size: 30px;}.lyric {height: 400px;margin: 0 auto;text-align: center;width: 80%;border: 1px solid black;overflow: hidden;.lyric-container {margin-top: 1px;// overflow-y: scroll;width: 100%;height: 100%;transition: all 1s linear;.lyric-container::-webkit-scrollbar {display: none;}}}}
}
</style>

创建音乐模块

src/components/sider.vue

<Submenu name="1"><template slot="title"><Icon type="ios-paper" />创建的音乐</template><MenuItem:to="{ name: 'musicList', params: { id: item.id } }":name="item.id"v-for="item in userPlayList":key="item.id">{{ item.name }}</MenuItem></Submenu><Submenu name="2"><template slot="title"><Icon type="ios-paper" />收藏的音乐</template><MenuItem name="1-1">文章管理</MenuItem><MenuItem name="1-2">评论管理</MenuItem><MenuItem name="1-3">举报管理</MenuItem></Submenu>
<Drawer title="我的歌单" :closable="false" v-model="showList"><i-table:columns="columns":data="musicList"ellipsisheight="600"@on-row-dblclick="changeIndex"><template slot-scope="{ row }" slot="singal">{{ row.ar[0].name }}</template><template slot-scope="{ row }" slot="album">{{ row.al.name }}</template></i-table></Drawer>

视频模块

获取数据

src/api/index.js

// 获取视频tag
export const reqGetVideoTag = () => ajax(BASE + '/video/group/list', 'post')// 根据tag获取视频
export const reqGetVideo = (id, offset, time) => ajax(BASE + '/video/group?timestamp=' + time, { id, offset }, 'post')
配置路由

src/router/index.js

 {path: '/home/video',name: 'Video',redirect: '/home/video/home',component: () => import(/* webpackChunkName: "home" */ '../views/home/video.vue'),children: [{path: '/home/video/home',name: 'VideoHome',component: () => import(/* webpackChunkName: "video" */ '../views/home/video/video-home.vue')}]}
功能编写

src/views/home/video/video-home.vue

<template><div class="video-container"><div class="category"><Poptip placement="bottom-start" width="520"><Button shape="circle">全部视频</Button><div slot="content" class="category-btn"><Row><i-col span="4" v-for="tag in videoTags" :key="tag.id"><span @click="onClickTag(tag.id)">{{ tag.name }}</span></i-col></Row></div></Poptip></div><div class="videos"><Scroll :on-reach-bottom="handleReachBottom" height="450"><Row :gutter="16"><i-col span="6" v-for="item in videos" :key="item.data.vid"><div class="video"><img :src="item.data.coverUrl" /><h3>{{ item.data.title }}</h3><span class="author">by/{{ item.data.creator.nickname }}</span></div></i-col></Row></Scroll></div></div>
</template><script>
import { reqGetVideoTag, reqGetVideo } from '../../../api'
export default {data () {return {videoTags: [],currentTag: 0,videos: [],offset: 0}},methods: {// 获得所有标签async getVideoCategory () {const result = await reqGetVideoTag()this.videoTags = result.datathis.currentTag = this.videoTags[0].id},// 点击标签onClickTag (id) {this.currentTag = id},// 滑动到底部async handleReachBottom () {console.log('加载中...')const date = new Date()const result = await reqGetVideo(this.currentTag, this.offset, date.getTime())console.log(result)this.videos = [...this.videos, ...result.datas]this.offset += 8}},created () {this.getVideoCategory()},watch: {async currentTag (id) {const date = new Date()this.offset = 0const result = await reqGetVideo(id, this.offset, date.getTime())console.log(result)this.videos = result.datasthis.offset += 8}}
}
</script><style scoped lang='scss'>
.video-container {padding: 20px;.category {.category-btn {height: 400px;overflow-y: auto;}.ivu-col {text-align: center;margin-bottom: 10px;cursor: pointer;&:hover {color: var(--theme_color);}}}.videos {h3 {font-weight: bold;white-space: nowrap;overflow: hidden;text-align: center;}span {font-size: 15px;color: rgb(223, 17, 17);}.author {cursor: pointer;margin-left: 200px;&:hover {color: black;}}.video {img {height: 172px;width: 305.77px;margin-left: 100px;}margin-top: 20px;}}
}
</style>

网易云项目(Vue)相关推荐

  1. 01、Vue简易版网易云——项目简介

    1.项目介绍 在经过一段时间Vue的学习之后,本人决定用一个简易版的网易云项目,对Vue的知识进行进一步的巩固. 项目完整github地址 2.项目技术 1.后台技术 本次后台使用的是,别人写好的网络 ...

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

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

  3. 微信小程序之网易云项目实战(一)

    微信小程序之网易云项目实战(一) 1.全局app.json 配置 {"pages": ["pages/index/index","pages/logs ...

  4. 仿网易云项目前端服务器部署+Nodejs部署

    做了几天的仿网易云移动端项目,做出来了不知道怎么部署上线?搞了好久!!!!记录一下! 1.首先(优化 + 检查)项目 1.1 vue.config.js种配置: 安装 npm i compressio ...

  5. 网易云项目播放组件中的进度条拖动bug(elementUI)

    因遇到的bug相同,此处部分参考引用了以下文章:vue+element制作音乐播放器播放进度条bug(鼠标拖拽slider滑块滑动到指定位置无效)_hhhhhhhssss的博客-CSDN博客_vue音 ...

  6. 网易云项目 【前端】【黑马】

    参考文档: 网易云音乐接口下载地址:GitHub - Binaryify/NeteaseCloudMusicApi: 网易云音乐 Node.js API service网易云音乐 Node.js AP ...

  7. 网易云课堂vue在线翻译实站

    注意input 标签里面的type = submit ,和button按钮可不是一回事:前者一般用在form表单里面,是form表单的表单元素,而后者比较自由

  8. django项目 网易云音乐

    项目说明 网易云项目 语言:python 前端: bootstarp 后端: django 数据库:sqlite3 项目说明 python爬虫爬取网易云的歌曲名称和链接,并保存到数据库中 使用boot ...

  9. 网易云摸到了大象灵巧的鼻子

    "如果说市场上已有的云计算厂商摸到了大象的肚子,那么网易云则摸到了大象最灵巧的鼻子",6月1日,在面向全国百余家媒体进行的演讲分享中,网易杭州研究院副院长汪源这样概括网易的云战略, ...

最新文章

  1. java一般会写javadoc吗_怎么写javadoc
  2. 20155337祁家伟做中学
  3. WEB前端学习笔记01利用纯CSS书写二级水平导航菜单
  4. whatpulse.exe 启动时报错
  5. OpenMap教程第2部分–使用MapHandler构建基本地图应用程序–第1部分
  6. 网易云免费OSS服务用做Markdown图床或博客图片外链
  7. arcgis api for js入门开发系列六地图分屏对比(含源代码)
  8. Qt ui-setupUi(this)的作用
  9. springboot项目搭建(mybatis + thymeleaf)
  10. 3、RH850端口说明及及复用功能配置
  11. unity 导入STL格式模型(STL 文本ASCII码格式文件)
  12. (1)安装Arch系统 - 树莓派Raspberry Pi - Arch Linux(转载)
  13. CVPR2021 MotionRNN: A Flexible Model for Video Prediction with Spacetime-Varying Motions
  14. python文档学习
  15. 基于Android的废旧物品回收APP
  16. 豆豉烘干技巧有哪些,只用下列步骤烘干就行了
  17. 什么是重排和重绘?何时会触发?
  18. 为什么要上机械制造业ERP系统?对企业有什么帮助?
  19. map和multimap的用法详解
  20. python判断一个数是否为回文数

热门文章

  1. 想做游戏测试工程师?这几点不知道可不行!
  2. 将JAVA bean/实体类 中为null的属性值转换成空字符串
  3. java斗地主发牌_实现斗地主发牌(java)
  4. 图片去除水印两种工具及多种思路
  5. Python打印九九乘法口诀表
  6. Fast Reed-Solomon Interactive Oracle Proofs of Proximity学习笔记
  7. 海洋捕食者算法 MPA
  8. fluent-mybatis初体验
  9. 如何用数学课件制作工具奇数等分圆
  10. 元宇宙来了,用好名字好概念赋能中国制造国际营销的初步构想