设计内容:
音乐网站网站是一款提供在线播放音乐和下载音乐功能的网站,具有音乐排行榜、歌星排行榜、音乐分类三个板块,并提供查询功能,要求如下:
① 页面布局合理,色彩和谐,链接正确,图文并茂,网页总数不少于8页。
② 网站结构合理,对网站中的文件要按文件类型建立相应的文件夹存放。
③ 要使用HTML、CSS和Javascript技术
④ 以下技术根据自己的实际掌握情况可选:
1)HTML5
2)Jquery
3)Bootstrap
Vue.js(或React.js或Angular.js)
摘 要
音乐网站设计是设计开发一个音乐播发及音乐推荐平台,该平台可以根据用户的喜好推荐音乐,还可以为用户推荐当前热门歌曲和人气排行等大众喜欢的歌曲推荐给用户,满足用户的音乐需求。html、css、以及vue框架等技术实现了音乐播发及音乐推荐平台,此平台具有用户登录、歌曲搜索、热门歌曲推荐、用户喜欢歌曲推荐、歌曲排行推荐等主要功能。
关键词:音乐网站;需求分析;系统设计
1.课题描述
互联网式的音乐传播彻底改变了原有传统的音频传播途径与方式,方便的传播途径,高效的传播效率,有利于社会资源的最大化利用[5]。由于音乐平台使用方便,越来越受到大家地欢迎。但这也导致了中国的音乐网站平台数量的激增,网站为了能够有自己稳定的使用者,网站之间的竞争也空前激烈,为能在中国众多的音乐平台中起到领导地位,建立一个具有自己特色的,功能详细,实用的音乐播放及音乐推荐平台尤为重要。
音乐网站使用电脑对音乐进行管理,给用户一个良好的视觉呈现成果,通过页面元素的布局让用户直接可以进行查询搜索功能操作,进行登录操作后进行个人信息的设计以及对自己喜欢歌曲的管理。
2 应用技术
本次实训是关于音乐网站的设计,主要是进行计算机前端内容的编写,前端开发是创建WEB页面或APP等前端界面呈现给用户的过程,通过HTML,CSS及JavaScript以及衍生出来的各种技术,本文用的是VUE框架,解决方案,来实现互联网产品的用户界面交互。
2.1HTML5
HTML5 是定义 HTML 标准的最新的版本。该术语通过两个不同的概念来表现:它是一个新版本的HTML语言,具有新的元素,属性和行为,它有更大的技术集,允许构建更多样化和更强大的网站和应用程序。这个集合有时称为HTML5和它的朋友们,不过大多数时候仅缩写为一个词 HTML5。
它能够让你更恰当地描述你的内容是什么。能够让你和服务器之间通过创新的新技术方法进行通信。能够让网页在客户端本地存储数据以及更高效地离线运行。使 video 和 audio 成为了在所有 Web 中的一等公民。提供了一个更加分化范围的呈现选择。提供了非常显著的性能优化和更有效的计算机硬件使用。能够处理各种输入和输出设备及样式设计。
2.2 CSS
CSS是CascadingStyle Sheets (层叠样式表)的简称,是一组格式设置的规则和外观样式的定义M。CSS在Web应用程序界面中起到重要的作用:CSS简化了网页的代码,提高了网页的访问速度。因为外部的CSS文件会被浏览器保存在缓存中,从而加快了页面显示的速度,同时也减少了上传的代码量;CSS让网页更容易维护,其便于修改网站的样式,只需要修改CSS文件就可以改变整个网站的类型特色,避免了一个一个网页的修改,大大的减少了开发人员的工作量,而且已经定义过的css文件可以重用,即重用原来网页的样式;css使网页的功能更为强大,用户可以根据自己的需求来定义页面的显示类型等以及一些页面的特效等;同时,它将页面显示的内容与显示样式相分离,两者可以是单独的文件即.html文件和.css 文件,便于开发人员的修改和维护。
2.3 js
JavaScript是一种属于网络的脚本语言,已经被广泛应用于Web开发,常用于为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript是通过嵌入在HTML里来实现其功能。
1、是一种解释性脚本语言。
2、主要用来向HTML页面添加交互行为的语言。
3、可以直接嵌入HTML页面,但写成单独的JS文件更加的有利于代码的分离,利于后期的维护。
4、跨平台特性,能够在绝大多数浏览器的支持下,可以在多种平台下运行。
2.4 vue框架
Vue 是一款用于构建用户界面的JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
2.5数据库MYSQL
数据库(database)就是一个存储数据的仓库。为了方便数据的管理与储存,它将数据按照特定的条件存储在磁盘上。通过数据库管理系统,可以高效的处理和管理存储在数据库中的数据。而且MySQL数据库可以说是是目前运行速度最快的SQL语言数据库。具有快捷、便捷、易用、高效、安全、大批量等优点。
3 需求分析
需求分析是经过深入细致的调研和分析,准确理解用户和项目的功能、性能、可靠性等具体要求,将用户非形式的需求表述转化为完整的需求定义,从而确定系统必须做什么的过程。本系统是一个音乐网站的设计,为了给用户提供一个听歌且为用户推荐其个人感兴趣歌曲的平台。用户注册成为系统用户后可以对系统中的歌曲进行播放,在系统的页面搜索中查询歌曲,用户可以给歌曲进行评论,平台会展示热门歌曲给用户,为用户推荐个性化的歌曲,用户在平台中管理自己的信息。
3.1功能分析
音乐播放及音乐推荐平台主要功能是让用户满足听歌并为用户推荐歌曲的作用。用户在系统中可以查询歌曲信息、对歌曲进行评价,为用户提供个性化的服务。
本系统主要模块:
1.用户登录
登录是为保障系统的安全,用户输入账号密码然后进行登录进入系统主界面。
2.主界面
主界面是系统的主要功能展示的界面,为用户提供最核心的功能以及指引用户操作。
3.歌曲搜索
可以搜索歌曲信息。
4. 热门歌曲推荐
将热门歌曲推荐给用户。
5.用户喜欢歌曲推荐
平台推荐用户可能喜欢的歌曲。
6. 歌曲排行推荐
将平台歌曲进行一个排名,给用户提供歌曲选择的信息。
7. 歌曲评论及展示
用户可以给歌曲评论及看其他用户评论。
8. 歌曲分类展示
可以将歌曲分类并展示用户查看。
3.2性能分析
每个用户自己的需要不同,考虑以后的发展和同时使用系统的情况,要求系统具备一下性能:
1)系统要满足用户注册登录功能,方便每个用户的使用。
系统要满足用户搜索功能,方便用户对音乐的选择。
4系统设计
系统设计是根据系统分析的结果,运用系统科学的思想和方法,设计出能最大限度满足所要求的目标的新系统的过程。系统设计内容,包括确定系统功能、设计方针和方法,产生理想系统并作出草案,通过收集信息对草案作出修正产生可选设计方案,将系统分解为若干子系统,进行子系统和总系统的详细设计并进行评价,对系统方案进行论证并作出性能效果预测。
4.1整体布局
页面整体布局 为头,主体和底部三部分,上中下三栏布局。头部导航栏和底部是固定的,当点击导航栏的首页,歌单,歌手,我的音乐,搜索框,登录和注册,在主体部分会显示相应内容,效果图如下图1所示。
图4.1整体布局图
5 网站与网页设计内容
内部部分主要通过下面几个模块来进行陈述,分别是注册部分,登录部分,导航栏部分,首页部分人,歌手部分,歌曲部分,我的音乐七大部分。
5.1注册部分
首先用户可以先注册账号 ,通过表单来输写个人信息,其中 用户名 密码,性别和生日为必填写内容,手机号,邮箱,地区为非必填项目。
图5.1注册图
<template>
<div class="info"><p class="title">编辑个人资料</p><hr/><div class="personal"><el-form :model="registerForm" class="demo-ruleForm" label-width="80px"><el-form-item prop="username" label="用户名"><el-input v-model="registerForm.username" placeholder="用户名"></el-input></el-form-item><el-form-item prop="password" label="密码"><el-input type="password" placeholder="密码" v-model="registerForm.password"></el-input></el-form-item><el-form-item label="性别"><el-radio-group v-model="registerForm.sex"><el-radio :label="0">女</el-radio><el-radio :label="1">男</el-radio></el-radio-group></el-form-item><el-form-item prop="phoneNum" label="手机"><el-input placeholder="手机" v-model="registerForm.phoneNum" ></el-input></el-form-item><el-form-item prop="email" label="邮箱"><el-input v-model="registerForm.email" placeholder="邮箱"></el-input></el-form-item><el-form-item prop="birth" label="生日"><el-date-picker type="date" placeholder="选择日期" v-model="registerForm.birth" style="width: 100%;"></el-date-picker></el-form-item><el-form-item prop="introduction" label="签名"><el-input type="textarea" placeholder="签名" v-model="registerForm.introduction" ></el-input></el-form-item><el-form-item prop="location" label="地区"><el-select v-model="registerForm.location" placeholder="地区" style="width:100%"><el-optionv-for="item in cities":key="item.value":label="item.label":value="item.value"></el-option></el-select></el-form-item></el-form><div class="btn"><div @click="saveMsg()">保存</div><div @click="goback">取消</div></div></div>
</div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'
import { cities } from '../assets/data/form'
import { updateUserMsg, getUserOfId } from '../api/index'export default {
name: 'info',
mixins: [mixin],
data: function () {return {registerForm: { // 注册username: '',password: '',sex: '',phoneNum: '',email: '',birth: '',introduction: '',location: ''},cities: []}
},
computed: {...mapGetters(['userId'])
},
created () {this.cities = cities
},
mounted () {this.getMsg(this.userId)
},
methods: {getMsg (id) {getUserOfId(id).then(res => {this.registerForm.username = res[0].usernamethis.registerForm.password = res[0].passwordthis.registerForm.sex = res[0].sexthis.registerForm.phoneNum = res[0].phoneNumthis.registerForm.email = res[0].emailthis.registerForm.birth = res[0].birththis.registerForm.introduction = res[0].introductionthis.registerForm.location = res[0].locationthis.registerForm.avator = res[0].avator}).catch(err => {console.log(err)})},goback () {this.$router.go(-1)},saveMsg () {let d = new Date(this.registerForm.birth)let datetime = d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()let params = new URLSearchParams()params.append('id', this.userId)params.append('username', this.registerForm.username)params.append('password', this.registerForm.password)params.append('sex', this.registerForm.sex)params.append('phone_num', this.registerForm.phoneNum)params.append('email', this.registerForm.email)params.append('birth', datetime)params.append('introduction', this.registerForm.introduction)params.append('location', this.registerForm.location)updateUserMsg(params).then(res => {if (res.code === 1) {this.showError = falsethis.showSuccess = truethis.$store.commit('setUsername', this.registerForm.username)this.$notify.success({title: '编辑成功',showClose: true})setTimeout(function () {this.$router.go(-1)}, 2000)} else {this.showSuccess = falsethis.showError = truethis.$notify.error({title: '编辑失败',showClose: true})}}).catch(err => {console.log(err)})}
}
}
</script>
5.2登录部分
首先用户可以先注册账号 ,通过表单来输写个人信息,其中 用户名 密码,性别和生日为必填写内容,手机号,邮箱,地区为非必填项目。
图5.2登录图
<script>
export default {data() {return {uname: '',upwd: '',isSee: false,tips: ''}},methods: {showInfo() {if (this.uname === '' && this.upwd === '') {this.isSee = true;this.tips = '用户名字密码不能为空';return;} else {this.isSee = false;if (this.uname === 'admin' && this.upwd === 'zzz') {this.$router.push({path: '/index:id',name: 'A',props:true,})} else {this.isSee = true;this.tips = "用户名或者密码错误";}}},}
}</script><template><div class="main"><h2>登录页面</h2><div class="form">用户名:<input type="text" name="username" v-model="uname"><br>密 码:<input type="password" name="password" v-model="upwd"><br><button v-on:click="showInfo">登录</button><br></div><h2 :class="{ tip: isSee }" v-show="isSee">{{ tips }}</h2></div>
</template>
导航栏部分代码如下:
<template><div class="the-header"><!--图标--><div class="header-logo" @click="goHome"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-erji"></use></svg><span>{{musicName}}</span></div><ul class="navbar" ref="change"><li :class="{active: item.name === activeName}" v-for="item in navMsg" :key="item.path" @click="goPage(item.path, item.name)">{{item.name}}</li><li><div class="header-search"><input type="text" placeholder="搜索音乐" @keyup.enter="goSearch()" v-model="keywords"><div class="search-btn" @click="goSearch()" ><svg class="icon" aria-hidden="true"><use xlink:href="#icon-sousuo"></use></svg></div></div></li><li v-show="!loginIn" :class="{active: item.name === activeName}" v-for="item in loginMsg" :key="item.type" @click="goPage(item.path, item.name)">{{item.name}}</li></ul><!--设置--><div class="header-right" v-show="loginIn"><div id="user"><img :src="attachImageUrl(avator)" alt=""></div><ul class="menu"><li v-for="(item, index) in menuList" :key="index" @click="goMenuList(item.path)">{{item.name}}</li></ul></div></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'
import { navMsg, loginMsg, menuList } from '../assets/data/header'export default {name: 'the-header',mixins: [mixin],data () {return {musicName: '个人-音乐',navMsg: [], // 左侧导航栏loginMsg: [], // 右侧导航栏menuList: [], // 用户下拉菜单项keywords: ''}
},computed: {...mapGetters(['userId','activeName','avator','username','loginIn'])
},created () {this.navMsg = navMsgthis.loginMsg = loginMsgthis.menuList = menuList
},mounted () {document.querySelector('#user').addEventListener('click', function (e) {document.querySelector('.menu').classList.add('show')e.stopPropagation()// 关键在于阻止冒泡}, false)// 点击“菜单”内部时,阻止事件冒泡。(这样点击内部时,菜单不会关闭)document.querySelector('.menu').addEventListener('click', function (e) {e.stopPropagation()}, false)document.addEventListener('click', function () {document.querySelector('.menu').classList.remove('show')}, false)
},methods: {goHome () {this.$router.push({path: '/'})},goPage (path, value) {document.querySelector('.menu').classList.remove('show')this.changeIndex(value)if (!this.loginIn && path === '/my-music') {this.notify('请先登录', 'warning')} else {this.$router.push({path: path})}},changeIndex (value) {this.$store.commit('setActiveName', value)},goMenuList (path) {if (path === 0) {this.$store.commit('setIsActive', false)}document.querySelector('.menu').classList.remove('show')if (path) {this.$router.push({path: path})} else {this.$store.commit('setLoginIn', false)this.$router.go(0)}},goSearch () {this.$store.commit('setSearchword', this.keywords)this.$router.push({path: '/search', query: {keywords: this.keywords}})}
}
}
5.3导航栏部分
导航部分是整个页面的头部部分,不会随着内容的改变而改变,采用的是tab切换,当点击导航栏部分的按钮,会在主页显示相应内容。
图5.3导航栏图
<template><div class="the-header"><!--图标--><div class="header-logo" @click="goHome"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-erji"></use></svg><span>{{musicName}}</span></div><ul class="navbar" ref="change"><li :class="{active: item.name === activeName}" v-for="item in navMsg" :key="item.path" @click="goPage(item.path, item.name)">{{item.name}}</li><li><div class="header-search"><input type="text" placeholder="搜索音乐" @keyup.enter="goSearch()" v-model="keywords"><div class="search-btn" @click="goSearch()" ><svg class="icon" aria-hidden="true"><use xlink:href="#icon-sousuo"></use></svg></div></div></li><li v-show="!loginIn" :class="{active: item.name === activeName}" v-for="item in loginMsg" :key="item.type" @click="goPage(item.path, item.name)">{{item.name}}</li></ul><!--设置--><div class="header-right" v-show="loginIn"><div id="user"><img :src="attachImageUrl(avator)" alt=""></div><ul class="menu"><li v-for="(item, index) in menuList" :key="index" @click="goMenuList(item.path)">{{item.name}}</li></ul></div></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'
import { navMsg, loginMsg, menuList } from '../assets/data/header'export default {name: 'the-header',mixins: [mixin],data () {return {musicName: '个人-音乐',navMsg: [], // 左侧导航栏loginMsg: [], // 右侧导航栏menuList: [], // 用户下拉菜单项keywords: ''}
},computed: {...mapGetters(['userId','activeName','avator','username','loginIn'])
},created () {this.navMsg = navMsgthis.loginMsg = loginMsgthis.menuList = menuList
},mounted () {document.querySelector('#user').addEventListener('click', function (e) {document.querySelector('.menu').classList.add('show')e.stopPropagation()// 关键在于阻止冒泡}, false)// 点击“菜单”内部时,阻止事件冒泡。(这样点击内部时,菜单不会关闭)document.querySelector('.menu').addEventListener('click', function (e) {e.stopPropagation()}, false)document.addEventListener('click', function () {document.querySelector('.menu').classList.remove('show')}, false)
},methods: {goHome () {this.$router.push({path: '/'})},goPage (path, value) {document.querySelector('.menu').classList.remove('show')this.changeIndex(value)if (!this.loginIn && path === '/my-music') {this.notify('请先登录', 'warning')} else {this.$router.push({path: path})}},changeIndex (value) {this.$store.commit('setActiveName', value)},goMenuList (path) {if (path === 0) {this.$store.commit('setIsActive', false)}document.querySelector('.menu').classList.remove('show')if (path) {this.$router.push({path: path})} else {this.$store.commit('setLoginIn', false)this.$router.go(0)}},goSearch () {this.$store.commit('setSearchword', this.keywords)this.$router.push({path: '/search', query: {keywords: this.keywords}})}
}
}
</script>
5.4首页部分
首页页面主体分为三部分,上面部分放了一个轮播图,中间部分是歌单,当用户点歌单的时候会跳到歌单页面同时在底部会播放音乐,下半部分是歌手,当点击歌手页面的时候会跳转到歌手页面,显示歌手的信息,效果图如下图所示。
图5.4首页图
5.5歌手部分
用户通过点击首页的歌手或者在导航栏点击歌手按钮 ,会显示男歌手女歌手以及组合歌手。
图5.5歌手图
template><div class="content"><h1 class="title"><slot name="title"></slot></h1><hr><ul><li class="list-title"><div class="song-item"><span class="item-index"></span><span class="item-title">歌曲名</span><span class="item-name">艺人</span><span class="item-intro">专辑</span></div></li><li class="list-content" v-for="(item, index) in songList" :key="index"><div class="song-item" :class="{'is-play': id === item.id}" @click="toplay(item.id, item.url, item.pic, index, item.name, item.lyric)"><span class="item-index"><span v-if="id !== item.id">{{index + 1}}</span><svg v-if="id === item.id" class="icon" aria-hidden="true"><use xlink:href="#icon-yinliang"></use></svg></span><span class="item-title">{{replaceFName(item.name)}}</span><span class="item-name">{{replaceLName(item.name)}}</span><span class="item-intro">{{item.introduction}}</span></div></li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'export default {name: 'album-content',mixins: [mixin],props: {songList: Array
},computed: {...mapGetters(['id' // 音乐ID])
}
}
</script>
5.6歌曲部分
用户点击歌曲的时候或者点击首页布局中的歌曲的时候,会显示歌曲分类华语粤语欧美日韩轻音乐BGM乐器。
template><div class="content"><h1 class="title"><slot name="title"></slot></h1><hr><ul><li class="list-title"><div class="song-item"><span class="item-index"></span><span class="item-title">歌曲名</span><span class="item-name">艺人</span><span class="item-intro">专辑</span></div></li><li class="list-content" v-for="(item, index) in songList" :key="index"><div class="song-item" :class="{'is-play': id === item.id}" @click="toplay(item.id, item.url, item.pic, index, item.name, item.lyric)"><span class="item-index"><span v-if="id !== item.id">{{index + 1}}</span><svg v-if="id === item.id" class="icon" aria-hidden="true"><use xlink:href="#icon-yinliang"></use></svg></span><span class="item-title">{{replaceFName(item.name)}}</span><span class="item-name">{{replaceLName(item.name)}}</span><span class="item-intro">{{item.introduction}}</span></div></li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'export default {name: 'album-content',mixins: [mixin],props: {songList: Array
},computed: {...mapGetters(['id' // 音乐ID])
}
}
</script>
5.7我的音乐部分
用户下载的音乐在我的音乐里,会显示音乐名字,歌手名字和专辑名字。
<template><div class="upload"><p class="title">修改头像</p><hr/><div class="section"><el-uploaddrag:action="uploadUrl()":show-file-list="false":on-success="handleAvatarSuccess":before-upload="beforeAvatarUpload"><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或<em>修改头像</em></div><div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过10M</div></el-upload></div></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'export default {name: 'upload',mixins: [mixin],data () {return {imageUrl: ''}
},computed: {...mapGetters(['userId'])
},methods: {uploadUrl () {return `${this.$store.state.configure.HOST}/user/avatar/update?id=${this.userId}`},handleAvatarSuccess (res, file) {if (res.code === 1) {this.imageUrl = URL.createObjectURL(file.raw)this.$store.commit('setAvator', res.avator)this.$message({message: '修改成功',type: 'success'})} else {this.notify('修改失败', 'error')}},beforeAvatarUpload (file) {const isJPG = file.type === 'image/jpeg'const isLt2M = file.size / 1024 / 1024 < 10if (!isJPG) {this.$message.error('上传头像图片只能是 JPG 格式!')}if (!isLt2M) {this.$message.error('上传头像图片大小不能超过 10MB!')}return isJPG && isLt2M}
}
}
</script>
6网站部署和测试
6.1搜索功能
搜索部分通过歌手和歌曲名搜索,歌手和歌曲的数据来源于数据数据库,如果在搜索中不能找到相应歌手或者相应歌曲,会 弹出来此歌手或者歌曲不存在的警告。
6.1.1通过歌曲查询
如下当输入歌曲“天下”的时候会显示歌曲的详细信息艺人和专辑,效果图如下
图6.1歌曲搜索图
<template><div class="search-song-Lists"><content-list :contentList="albumDatas" path="song-list-album"></content-list></div>
</template><script>
import ContentList from '../ContentList'
import mixin from '../../mixins'
import { getSongListOfLikeTitle } from '../../api/index'export default {name: 'search-song-Lists',mixins: [mixin],components: {ContentList
},data () {return {albumDatas: []}
},mounted () {this.getSearchList()
},methods: {getSearchList () {if (!this.$route.query.keywords) returngetSongListOfLikeTitle(this.$route.query.keywords).then(res => {if (!res.length) {this.notify('暂无该歌曲内容', 'warning')} else {this.albumDatas = res}})}
}
}
</script>
6.1.2通过歌手搜索查询
在导航栏的搜索框里输入歌手的名字 ,会显示数据库中所有关于歌手的歌单,例如搜索查询歌手“张杰”,会显示他的所有歌曲,如果没有,则会判断弹出来警告,没有此歌手。
图6.2歌手搜索图
<template><div class="search-songs"><album-content :songList="listOfSongs"></album-content></div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../../mixins'
import AlbumContent from '../AlbumContent'export default {name: 'search-songs',mixins: [mixin],components: {AlbumContent
},computed: {...mapGetters(['listOfSongs' // 存放的音乐])
},mounted () {this.getSong()
}
}
</script>
6.2播放部分
当用户点击音乐的时候,会进行音乐的播放与停止操作。
图6.3播放图
<template>
<div class="play-bar" :class="{show:!toggle}"><div @click="toggle=!toggle" class="item-up" :class="{turn: toggle}"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-jiantou-xia-cuxiantiao"></use></svg></div><div class="kongjian" ><!--上一首--><div class="item" @click="prev"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-ziyuanldpi"></use></svg></div><!--播放--><div class="item" @click="togglePlay"><svg class="icon" aria-hidden="true"><use :xlink:href="playButtonUrl"></use></svg></div><!--下一首--><div class="item" @click="next"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-ziyuanldpi1"></use></svg></div><!--歌曲图片--><div class="item-img" @click="goPlayerPage"><img :src=picUrl alt=""></div><!--播放进度--><div class="playing-speed"><!--播放开始时间--><div class="current-time">{{ nowTime }}</div><div class="progress-box"><div class="item-song-title"><div>{{this.title}}</div><div>{{this.artist}}</div></div><div ref="progress" class="progress" @mousemove="mousemove"><!--进度条--><div ref="bg" class="bg" @click="updatemove"><div ref="curProgress" class="cur-progress" :style="{width: curLength+'%'}"></div></div><!--进度条 end --><!--拖动的点点--><div ref="idot" class="idot" :style="{left: curLength+'%'}" @mousedown="mousedown" @mouseup="mouseup"></div><!--拖动的点点 end --></div></div><!--播放结束时间--><div class="left-time">{{ songTime }}</div></div><!--音量--><div class="item icon-volume" ><svg v-if="volume !== 0" class="icon" aria-hidden="true"><use xlink:href="#icon-yinliang1"></use></svg><svg v-else class="icon" aria-hidden="true"><use xlink:href="#icon-yinliangjingyinheix"></use></svg><el-slider class="volume" v-model="volume" :vertical="true"></el-slider></div><!--添加--><div class="item" @click="collection"><svg :class="{ active: isActive }" class="icon" aria-hidden="true"><use xlink:href="#icon-xihuan-shi"></use></svg></div><!--下载--><div class="item" @click="download"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-xiazai"></use></svg></div><!--歌曲列表--><div class="item" @click="changeAside"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-liebiao"></use></svg></div></div>
</div>
</template><script>
import { mapGetters } from 'vuex'
import mixin from '../mixins'
import { setCollection, download } from '../api/index'export default {
name: 'play-bar',
mixins: [mixin],
data () {return {tag: false,nowTime: '00:00',songTime: '00:00',curLength: 0, // 进度条的位置progressLength: 0, // 进度条长度mouseStartX: 0, // 拖拽开始位置toggle: true,volume: 50}
},
computed: {...mapGetters(['loginIn', // 用户登录状态'userId', // 用户 id'isPlay', // 播放状态'playButtonUrl', // 播放状态的图标'id', // 音乐id'url', // 音乐地址'title', // 歌名'artist', // 歌手名'picUrl', // 歌曲图片'curTime', // 当前音乐的播放位置'duration', // 音乐时长'listOfSongs', // 当前歌单列表'listIndex', // 当前歌曲在歌曲列表的位置'showAside', // 是否显示侧边栏'autoNext', // 用于触发自动播放下一首'isActive'])
},
watch: {// 切换播放状态的图标isPlay (val) {if (val) {this.$store.commit('setPlayButtonUrl', '#icon-zanting')} else {this.$store.commit('setPlayButtonUrl', '#icon-bofang')}},volume () {this.$store.commit('setVolume', this.volume / 100)},// 播放时间的开始和结束curTime () {this.nowTime = this.formatSeconds(this.curTime)this.songTime = this.formatSeconds(this.duration)// 移动进度条this.curLength = (this.curTime / this.duration) * 100// 处理歌词位置及颜色},// 自动播放下一首autoNext () {this.next()}
},
mounted () {this.progressLength = this.$refs.progress.getBoundingClientRect().widthdocument.querySelector('.icon-volume').addEventListener('click', function (e) {document.querySelector('.volume').classList.add('show-volume')e.stopPropagation()}, false)document.querySelector('.volume').addEventListener('click', function (e) {e.stopPropagation()}, false)document.addEventListener('click', function () {document.querySelector('.volume').classList.remove('show-volume')}, false)
},
methods: {// 下载download () {download(this.url).then(res => {let content = res.datalet eleLink = document.createElement('a')eleLink.download = `${this.artist}-${this.title}.mp3`eleLink.style.display = 'none'// 字符内容转变成blob地址let blob = new Blob([content])eleLink.href = URL.createObjectURL(blob)// 触发点击document.body.appendChild(eleLink)eleLink.click()// 然后移除document.body.removeChild(eleLink)}).catch(err => {console.log(err)})},changeAside () {let temp = !this.showAsidethis.$store.commit('setShowAside', temp)},// 控制音乐播放 / 暂停togglePlay () {if (this.isPlay) {this.$store.commit('setIsPlay', false)} else {this.$store.commit('setIsPlay', true)}},// 解析播放时间formatSeconds (value) {let theTime = parseInt(value)let theTime1 = 0let theTime2 = 0if (theTime > 60) {theTime1 = parseInt(theTime / 60) // 分theTime = parseInt(theTime % 60) // 秒// 是否超过一个小时if (theTime1 > 60) {theTime2 = parseInt(theTime1 / 60) // 小时theTime1 = 60 // 分}}// 多少秒if (parseInt(theTime) < 10) {var result = '0:0' + parseInt(theTime)} else {result = '0:' + parseInt(theTime)}// 多少分钟时if (theTime1 > 0) {if (parseInt(theTime) < 10) {result = '0' + parseInt(theTime)} else {result = parseInt(theTime)}result = parseInt(theTime1) + ':' + result}// 多少小时时if (theTime2 > 0) {if (parseInt(theTime) < 10) {result = '0' + parseInt(theTime)} else {result = parseInt(theTime)}result = parseInt(theTime2) + ':' + parseInt(theTime1) + ':' + result}return result},// 拖拽开始mousedown (e) {this.mouseStartX = e.clientXthis.tag = true},// 拖拽结束mouseup () {this.tag = false},// 拖拽中mousemove (e) {if (!this.duration) {return false}if (this.tag) {let movementX = e.clientX - this.mouseStartXlet curLength = this.$refs.curProgress.getBoundingClientRect().width// 计算出百分比this.progressLength = this.$refs.progress.getBoundingClientRect().widthlet newPercent = ((curLength + movementX) / this.progressLength) * 100if (newPercent > 100) {newPercent = 100}this.curLength = newPercentthis.mouseStartX = e.clientX// 根据百分比推出对应的播放时间this.changeTime(newPercent)}},// 更改歌曲进度changeTime (percent) {let newCurTime = this.duration * (percent * 0.01)this.$store.commit('setChangeTime', newCurTime)},updatemove (e) {if (!this.tag) {let curLength = this.$refs.bg.offsetLeftthis.progressLength = this.$refs.progress.getBoundingClientRect().widthlet newPercent = ((e.clientX - curLength) / this.progressLength) * 100if (newPercent < 0) {newPercent = 0} else if (newPercent > 100) {newPercent = 100}this.curLength = newPercentthis.changeTime(newPercent)}},// 上一首prev () {if (this.listIndex !== -1 && this.listOfSongs.length > 1) {if (this.listIndex > 0) {this.$store.commit('setListIndex', this.listIndex - 1)this.toPlay(this.listOfSongs[this.listIndex].url)} else {this.$store.commit('setListIndex', this.listOfSongs.length - 1)this.toPlay(this.listOfSongs[this.listIndex].url)}}},// 下一首next () {if (this.listIndex !== -1 && this.listOfSongs.length > 1) {if (this.listIndex < this.listOfSongs.length - 1) {this.$store.commit('setListIndex', this.listIndex + 1)this.toPlay(this.listOfSongs[this.listIndex].url)} else {this.$store.commit('setListIndex', 0)this.toPlay(this.listOfSongs[0].url)}}},// 选中播放toPlay (url) {if (url && url !== this.url) {this.$store.commit('setId', this.listOfSongs[this.listIndex].id)this.$store.commit('setUrl', this.$store.state.configure.HOST + url)this.$store.commit('setpicUrl', this.$store.state.configure.HOST + this.listOfSongs[this.listIndex].pic)this.$store.commit('setTitle', this.replaceFName(this.listOfSongs[this.listIndex].name))this.$store.commit('setArtist', this.replaceLName(this.listOfSongs[this.listIndex].name))this.$store.commit('setLyric', this.parseLyric(this.listOfSongs[this.listIndex].lyric))}},goPlayerPage () {this.$router.push({path: `/lyric/${this.id}`})},collection () {if (this.loginIn) {var params = new URLSearchParams()params.append('userId', this.userId)params.append('type', 0) // 0 代表歌曲, 1 代表歌单params.append('songId', this.id)setCollection(params).then(res => {if (res.code === 1) {this.$store.commit('setIsActive', true)this.notify('收藏成功', 'success')} else if (res.code === 2) {this.notify('已收藏', 'warning')} else {this.$notify.error({title: '收藏失败',showClose: false})}}).catch(err => {console.log(err)})} else {this.notify('请先登录', 'warning')}}
}
}
</script>
7总结
本次课设,通过自己对相关系统进行分析以及设计,使我对信息系统分析与设计这门课有了更深层次的理解以及对PD建模工具使用的更加得心应手。在课程设计过程中,首先要从需求分析出发,对系统进行深入分析。需求分析是前提,只有做好了分析,设计的系统才能符合用户的需求,对前端的内容html,css,js的初步了解通过查阅有关图书信息,以及回忆之前所学内容,运用vue框架完成了本次实训,总结了很多有价值有意义的学习经验,还能让我把以前学到的知识加以巩固并且进一步的提高对现在所学知识的理解,让我认识到了自己在以前学习方面的不足之处。以后还需要继续努力。
参考文献
[1]王昊,刘友华.信息系统分析与设计[M].江苏:南京大学出版社,2021.
[2]张俊玲.数据库原理与应用[M].北京:清华大学出版社,2010.
[3]苗雪兰,刘瑞新,宋歌.数据库系统原理[M]. 北京:机械工业出版社,2007.
[4]刘亚军,高莉莎.数据库设计及应用[M]. 北京:清华大学出版社,2007.
[5]何东健.网页设计与Web编程[M]. 西安:西安交通大学出版社,2004.