# 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 } } ```