项目码云地址:https://gitee.com/Snow28/MyMusic(如果觉得可以,麻烦给我鼓励一个star)
这个是基于Vue2.0,vuex,vue-router,axios和html5的flexible box布局与及css3的transform,animation,transition组成。
项目是由vue-cli脚手架搭建的,
关于src文件中源代码的内容:
api处理:
项目数据全是线上抓取的,在qq音乐的官网可以抓取文件的url和相应的请求参数。
在抓取数据过程中,遇到了跨域问题,ajax并不能实现跨域,于用到了jsonp技术。在common文件夹下封装了jsonp的请求方法,
export default function jsonp(url, data, option) {
url += (url.indexOf('?') < 0 ? '?' : '&') + param(data)
return new Promise((resolve, reject) => {
originJsonp(url, option, (err, data) => {
if (!err) {
resolve(data)
} else {
reject(err)
}
})
})
}
export function param(data) {
let url = ''
for (var k in data) {
let value = data[k] !== undefined ? data[k] : ''
url += '&' + k + '=' + encodeURIComponent(value)
}
return url ? url.substring(1) : ''
}
处理了url的拼接和转码。
在base文件中,写的是在很多地方相同的功能组件,例如:搜索框组件,搜索列表主键,滚动功能组件,歌去列表展示组件,tab导航组件。
在comon文件中,存放了静态的字体文件,图片文件,stylus样式文件,还有一些封装好功能的js组件(如:数据缓存功能,还有不能jsonp请求的接口,需要用vue中proxy做代理的,还有在多个vue组件中都会用到的方法写在mixin.js文件中)
components文件就是我们开发主要功能目录
router文件中定义一个index.js文件,在里面管理路由的跳转
store文件中主要负责vuex代码,定义数据的state状态,修改并相应数据状态
main.js入口文件代码如下所示:
在入口文件引入了vue-router路由,store的数据存储状态,vuex全局状态管理,fastclick(避免移动端点击延迟300ms的效果)以及vue-lazyload懒加载模块。
vue-router引入页面路由,对页面组件进行导航,其中其他引入大多是自定义的页面组件:
store文件中:
关于vuex全局保存各个组件共享的数据与及改变数据的逻辑,全部保存在store的index.js里面,这是关键文件。包括利用数组模拟队列进行播放歌曲的保存,搜索历史的保存等关键逻辑。
App.vue组件
是整个项目的根目录组件,在这里挂载其他所有的组件。m-header是QQ音乐的头部,tab组件是导航栏部分。router-view是将点击tab单项匹配到路由组件加载到这块内容。player就是音乐控制面板,当音乐详情关闭,则就以小化的面板控制音乐。
vuex集中状态管理 :
完成该项目的难点:
数据抓取
存储本地数据和浏览器数据的业务逻辑,页面刷新后,数据状态不会改变
组件的复用
better-scroll 移动端插件(每次dom渲染要重新计算scroll宽度),封装成组件。
vuex状态管理的项目结构设计
几种历史记录收藏列表存储在localStorage
问题描述:
遇到动态路由如:/page/:id
从/page/1 切换到 /page/2 发现页面组件没有更新
解决方式:
给
...
computed:{
key(){
return this.$route.path + Math.random();
}
}
问题描述:
在a页面写一个定时器,每秒钟打印一次,然后跳转到b页面,此时可以看到,定时器依然在执行。
推荐的解决方式:
通过$once这个事件侦听器器在定义完定时器之后的位置来清除定时器。
const timer = setInterval(() => {
// 定时器操作
}, 1000)
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {
clearInterval(timer);
})
1.使用resolve => require(['./ComponentA'], resolve),方法如下:
const ComponentA = resolve => require(['./ComponentA'], resolve)
const ComponentA = () => import('./ComponentA')
组件之间的通信方案:
实现细节可参考:https://m.jb51.net/article/167304.htm
...
methods: {
fun(e) {
// e.target 是你当前点击的元素
// e.currentTarget 是你绑定事件的元素
#获得点击元素的前一个元素
e.currentTarget.previousElementSibling.innerHTML
#获得点击元素的第一个子元素
e.currentTarget.firstElementChild
# 获得点击元素的下一个元素
e.currentTarget.nextElementSibling
# 获得点击元素中id为string的元素
e.currentTarget.getElementById("string")
# 获得点击元素的string属性
e.currentTarget.getAttributeNode('string')
# 获得点击元素的父级元素
e.currentTarget.parentElement
# 获得点击元素的前一个元素的第一个子元素的HTML值
e.currentTarget.previousElementSibling.firstElementChild.innerHTML
}
},