# myplayer
**Repository Path**: simple-cell/myplayer
## Basic Information
- **Project Name**: myplayer
- **Description**: No description available
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2021-11-04
- **Last Updated**: 2022-03-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## MyPlayer项目开发文档
### 一、基本布局
1.aside模块:上方区域放置logo和名称,下方区域放置一个menu菜单以实现点击切换main页面的功能
2.header模块:以浮动的方式将arrow箭头模块,搜索框模块和用户模块按顺序布局在header模块中,实现切换历史页面,搜索歌曲和用户登录的内容
3.main模块:呈现歌曲内容的主体区域,通过组件的形式引入home主页面
4.footer模块:展示歌曲一些信息和控制歌曲播放
5.playlist模块:显示歌曲的播放列表,实现歌曲列表的增添与删除功能
### 二、功能实现
#### 1.登录功能的实现
Login.vue
```
login () {
if (this.username === 'admin' && this.password === '666666') {
// 登录成功
// 1. 存储 token
localStorage.setItem('token', 'Bearer xxxx')
// 2. 跳转到后台主页
this.$router.push('/home')
} else {
// 登录失败
localStorage.removeItem('token')
}
}
```
Home.vue
```
logout () {
localStorage.removeItem('token')
this.$router.push('/login')
}
```
index.js
```
router.beforeEach(function (to, from, next) {
if (to.path === '/home') {
const token = localStorage.getItem('token')
if (token) {
next()
} else {
next('/login')
}
} else {
next()
}
})
```
#### 2.点击侧边栏选项进行路由跳转,实现切换主页面内容
```html
推荐
```
```vue
const routes = [
{ path: '/', redirect: '/home' },
{
path: '/home',
component: Home,
redirect: '/home/recommend',
children: [
{ path: 'recommend', component: Recommend },
{ path: 'musichall', component: Musichall },
{ path: 'video', component: Video },
{ path: 'radio', component: Radio },
{ path: 'favor', component: Favor },
{ path: 'download', component: Download },
{ path: 'recent', component: Recent },
{ path: 'search', component: Search }
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
```
#### 3.点击header模块的左右箭头实现路由的向前向后跳转
```
```
注意:this.$router是路由的“导航对象”,this.$route 是路由的“参数对象”。在行内使用编程式导航跳转的时候,this 必须要省略。
#### 4.搜索功能的实现
Home.vue
在搜索框输入要搜索的歌曲、歌手,使用v-model双向绑定输入的值,按下回车触发change事件,调用searchMusic函数跳转到search页面,再通过dispatch调用定义在actions中的获取歌曲列表的函数
```
```
```
methods:{
searchMusic (val) {
this.$router.push('/home/search')
this.$store.dispatch('searchMusicList', val)
}
}
```
store.js
由于actions中不能直接修改state中的数据,因此调用定义在mutations中的initList来得到修改后的歌曲列表
```
state: {
// 搜索结果的音乐列表
searchList: []
}
```
```
actions: {
async searchMusicList (context, input) {
const { data: res } = await axios.get('http://music.eleuu.com/search?keywords=' + input)
const result = res.result.songs
result.forEach((item) => { // 将毫秒数转为分秒
const minute = Math.floor((item.duration / 1000 / 60) >> 0)
const second = Math.floor((item.duration / 1000) % 60)
item.duration = `${minute}:${second > 10 ? second : '0' + second}`
})
context.commit('initList', result)
}
}
```
```
mutations: {
initList (state, val) {
state.searchList = val
}
}
```
#### 5.歌曲播放功能的实现(同时显示相关信息并加入播放列表)
search.vue
点击播放图标调用playMusic函数
```
```
```
methods: {
// 播放音乐功能
playMusic (val) {
// 根据id获取url值,将其定义为全局数据
this.$store.dispatch('getMusicUrl', val)
this.$store.dispatch('getMusicDetail', val)
// 将其加入到播放列表
this.addMusic(val)
}
}
```
store.js
根据参数id获取从接口获取歌曲的url,歌曲名,歌手名等信息,将其定义为全局变量,从而在Home.vue使用
```
actions: {
// 获取歌曲url
async getMusicUrl (context, val) {
const { data: res } = await axios.get('http://music.eleuu.com/song/url?id=' + val)
context.commit('initUrl', res.data[0].url)
},
// 获取歌曲详情
async getMusicDetail (context, val) {
const { data: res } = await axios.get('http://music.eleuu.com/song/detail?ids=' + val)
context.commit('initMusicName', res.songs[0].name)
context.commit('initSingerName', res.songs[0].ar[0].name)
context.commit('initPicUrl', res.songs[0].al.picUrl)
}
}
```
```
mutations: {
// 定义全局使用的url值
initUrl (state, val) {
state.musicUrl = val
},
// 定义全局使用的歌曲名
initMusicName (state, val) {
state.musicName = val
console.log(val)
},
// 定义全局使用的歌手名
initSingerName (state, val) {
state.singerName = val
console.log(val)
},
initPicUrl (state, val) {
state.picUrl = val
console.log(val)
}
}
```
#### 6.音乐的播放与暂停功能实现
动态绑定playState属性,点击时调用togglePlayState函数,使用ref操作DOM改变歌曲的播放与暂停状态
```
```
```
togglePlayState () {
if (this.$refs.audio.paused) {
this.$refs.audio.play()
this.playState = 'el-icon-video-pause'
} else {
this.$refs.audio.pause()
this.playState = 'el-icon-video-play'
}
}
```
```
playState: 'el-icon-video-pause'
```
#### 7.切换上/下一首歌曲的功能实现
Home.vue
```
```
```
methods: {
lastMusic () {
if (this.$store.state.musicIndex === 0) {
this.$store.state.musicIndex = this.$store.state.playList.length - 1
}
this.$store.state.musicIndex--
this.$store.commit('toggleMusicId', this.$store.state.musicIndex)
this.$store.dispatch('getMusicUrl', this.$store.state.musicId)
this.$store.dispatch('getMusicDetail', this.$store.state.musicId)
},
nextMusic () {
if (this.$store.state.musicIndex === this.$store.state.playList.length - 1) {
this.$store.state.musicIndex = -1
}
this.$store.state.musicIndex++
this.$store.commit('toggleMusicId', this.$store.state.musicIndex)
this.$store.dispatch('getMusicUrl', this.$store.state.musicId)
this.$store.dispatch('getMusicDetail', this.$store.state.musicId)
}
}
```
store.js
```
// 获取上一首或下一首音乐的id
toggleMusicId (state, val) {
state.musicId = state.playList[val][0].id
}
```
#### 8.播放列表各功能实现
##### 1.播放歌曲
playlist.vue
```
```
```
methods: {
// 播放音乐
playMusic (val, index) {
// 获取当前歌曲的索引
this.$store.commit('initMusicIndex', index)
// 获取当前歌曲url
this.$store.dispatch('getMusicUrl', val)
// 获取当前歌曲详情
this.$store.dispatch('getMusicDetail', val)
}
}
```
##### 2.添加歌曲
search.vue
```
```
```
methods: {
addMusic (val) {
this.$store.commit('addToPlayList', val)
}
}
```
store.js
```
// 添加到播放列表
addToPlayList (state, val) { state.playList.push(state.searchList.filter((item) => item.id === val))
}
```
3.删除歌曲
```
```
#### 9、推荐首页实现
#### 10、歌单详情页面实现
### 三、难点解决
#### 1、body标签的默认margin值无法修改为0
解决方法:
1.新建全局样式表global.css并在main.js中引入,这样无论哪一个页面都会覆盖element-ui默认样式
2.去掉style标签的scoped属性或添加一个新的不带scoped属性的style标签
3.使用/deep/深度修改标签样式
4.通过内联样式或绑定类样式覆盖默认样式
#### 2、element.style内联样式中的值无法被修改
解决方法:
使用!important语法优先权对已经写死的样式进行修改
#### 3、elementui使用表格动态渲染数据
```
```
scope.row.id 表示获取该表格中每一行的id值
#### 4、点击arrow按钮回到历史页面时页面记录未保留
```
```
keep-alive可以将组件内容缓存,而不是销毁组件,这样进行路由跳转过后原来组件的已有内容会被保留
#### 5、升级Vue插件版本
1、安装升级插件npm-check-updates,安装方法npm install -g npm-check-updates;
2、查看项目中插件的当前版本和最新版本npm-check-updates(简写ncu);
3、升级需要的插件如vxe-table:ncu -u 插件名 (例如:ncu -u ant-design-vue)
#### 6、点击播放图标和点击添加图标将歌曲加入列表冲突
#### 7、跨域问题解决
问题:
Access to XMLHttpRequest at 'http://music.eleuu.com/personalized?limit=10' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'null' that is not equal to the supplied origin.
前后端分离开发时,不得不面对跨域问题。对于跨域,可以用两种办法进行处理。
1.安装nginx,将后端和前端都代理带nginx上。
2.在vue-cli中配置proxy,将API请求代理到API服务器上。设置devServer.proxy
解决方法:
配置vue.config.js文件
```
module.exports = {
lintOnSave: false, // 是否开启eslint
devServer: {
open: true, // 是否自动弹出浏览器页面
host: 'localhost',
port: '8080',
https: false, // 是否使用https协议
hotOnly: false, // 是否开启热更新
proxy: {
'/api': {
// 目标服务器,代理访问到http://music.eleuu.com/
target: 'http://music.eleuu.com/',
// 允许跨域
changeOrigin: true,
ws: true, // 是否代理websockets
pathRewrite: {
'^/api': '' // 重写路径 比如'/api/aaa/ccc'重写为'/aaa/ccc'
}
}
}
}
}
```
#### 8、循环调用axios异步请求,实现同步
问题:
传入歌单id获取到的所有音乐不是完整的,可拿全部 trackIds 请求一次 `song/detail` 接口获取所有歌曲的详情
解决方法:
使用map结合Promise实现
map()方法会得到一个新的数组并返回;(适用于要改变数据值的时候)
forEach()方法不会返回执行结果,而是undefined;(适用于并不打算改变数据的时候,而只是想用数据做一些事情:比如存入数据库或打印出来)
```
getSongs (val) {
const result = []
const arr = val.playlist.trackIds
arr.map(item => {
return new Promise((resolve, reject) => {
this.$http.get('http://music.eleuu.com/song/detail?ids=' + item.id).then(data => {
const minute = Math.floor((data.data.songs[0].dt / 1000 / 60) >> 0)
const second = Math.floor((data.data.songs[0].dt / 1000) % 60)
data.data.songs[0].dt = `${minute}:${second > 10 ? second : '0' + second}`
result.push(data)
})
})
})
this.songs = result
}
}
```
#### 8、页面刷新的状态下保留数据
进行F5页面刷新的时候,页面的数据会丢失,出现这个问题的原因是因为当用vuex做全局状态管理的时候,store中的数据是保存在运行内存中的,页面刷新时会重新加载vue实例,store中的数据就会被重新赋值,因此数据就丢失了,解决方式如下:
方法1、利用localStorage/sessionStorage将数据储存在外部,做一个持久化储存,通过监听beforeunload事件来进行数据的localStorage存储,beforeunload事件在页面刷新时进行触发,具体做法是在App.vue的created()周期函数中下如下代码:
```
export default {
name: 'App',
created () {
//在页面加载时读取localStorage里的状态信息
if (window.localStorage.getItem('list')) {
//this.$store.replaceState()替换 store 的根状态
this.$store.replaceState(Object.assign({}, this.$store.state,
JSON.parse(window.localStorage.getItem('list'))))
}
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener('beforeunload', () => {
window.localStorage.setItem('list', JSON.stringify(this.$store.state))
})
}
}
```
方法2:由此得知计算属性的结果会被缓存,也就是说在有缓存的情况下,computed会优先使用缓存,于是也可以在state数据相对应的页面这样写:
```
computed:{
orderList() {
return this.$store.state.orderList
}
}
```