算是学习Vue2.0的第二个项目,基本上是界面是模仿的网易云,但是交互逻辑更像qq音乐,目前项目的数据来源是qq音乐,但是已经崩了一个音乐资源,所以用的是一个大佬的接口 BZQLL。接下来打算在搭完服务器后上线项目。
ios端第一次播放音乐需要在页面随机点击 电脑端请在手机模式下体验
- vue + vuex + router
- better-scroll (页面上下滑动 也可用于 左右滑动)
- vue-lazyload (懒加载)
- swiper (页面左右滑动)
- axios (请求数据)
- good-storage (localStorage的包装)
打开:/etc/nginx/conf.d下的default.conf文件
upstream nirean-music {
server 127.0.0.1:9000;
}
server{
listen 80; #监听端口
server_name m.nirean.cn; #申请的域名
access_log /var/log/nginx/music.log; #记录运行日志的地方
error_log /var/log/nginx/music_err.log; //记录错误日志
location / {
root /usr/music-player/dist; #网页存放目录
index index.html index.html; #主页面
proxy_set_header Host $host; # #header添加请求host信息
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 增加代理记录
proxy_pass http://nirean-music; #服务A访问地址
}
}
PM2 start prod.server.js 在127.0.0.1:9000 端口运行prod.server.js
#数据请求
首先通过NPM安装JSONP包,通过import引入originJSONP(url,opts,callback)
- url 访问地址 其他参数加在地址后面 ?name="me"&age=15
- opts对象
- param callback的别名
- timeout
- prefix
- name
- 回调函数(err,data)两个参数
originJSONP(url,option,(err,data)=>{
if(!err){
resolve(data); //pomise已成功 返回数据
}
else{
reject(err); // promise已失败
}
});
通过promise实现对ajax技术的一种封装
var axios = require("axios");
axios.get(url,{
params:{ //`params` 是即将与请求一起发送的 URL 参数 get
},
headers:{ //`headers` 是即将被发送的自定义请求头
},
data:{ //`data` request body 中的数据 post
}
}).then((response)=>{
console.log(response.data); //数据
console.log(response.status); //状态码
console.log(response.statusText);//状态消息
console.log(response.headers); //服务器回应的头
console.log(response.config); //提供axios请求的配置信息
}).catch((error)=>{
//出错处理
})
例如在本项目中
const url = '/api/disc'; //后台服务器的路由地址
const data = Object.assign({}, commonParam, { //查看api需要的参数
disstid,
type: 1,
json: 1,
utf8: 1,
onlysong: 0,
platform: 'yqq',
loguin:0,
hostUin: 0,
needNewCode: 0,
g_tk:5381,
format:'json'
}) //发起get请求的参数
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data) //返回Promise对象
})
express是node.js的一个web应用程序框架
var express = require("express");
var app = express();
app.METHOD(PATH, HANDLER)
- app是express实例
- METHOD 是HTTP请求方法 get,post,put,delete等
- PATH 是 服务器上的路径
- HANDLER 是 在路由匹配时执行的函数 (req,res) 请求 响应
- 响应方法
- res.download() 提示将要下载文件
- res.end() 结束相应进程
- res.json() 返回JSON响应
- res.jsonp() 在 JSONP 的支持下发送 JSON 响应。
- res.send() 发送各种类型的响应
- res.sendFile() 发送文件
- res.sendStatus() 状态吗
- res.redirect() 重定向
- res.render()
- 响应方法
通过req获得params,发起axios请求
var url = 'https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg'
axios.get(url, {
headers: { //根据qq音乐的api的头部信息
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query //获得请求中的参数
}).then((response) => {
var ret = response.data //获得返回数据
if (typeof ret === 'string') {
var reg = /^\w+\(({[^()]+})\)$/
var matches = ret.match(reg)
if (matches) {
ret = JSON.parse(matches[1])
}
}
res.json(ret) //以json写入响应中
}).catch((e) => {
console.log(e)
})
- header 头部 嵌入了 搜索栏 以及 用户中心
- tab 用于3个页面的切换
- component 放入了3个大组件,分别是推荐、排行、歌手
- player 播放器 通过vuex来控制是否显示大播放器还是小播放器
- confirm 用于显示 提示信息 或者 歌曲列表,比如 添加歌曲成功 删除成功 是否删除歌曲 等 (主要时为了能保证它能显示在界面最顶层才出此下策)
通过swiper实现3个tab左右滑动切换,能实现一级路由,二级路由就不行了。 主要参考我的一个demo
主要通过
this.mySwiper.scrollbar.$el.css({}) //整个滚动条
this.mySwiper.scrollbar.$dragEl.css({}) //高亮部分
兼容情况比较坑,记得设置 width:100% left:0
否则会被默认值覆盖
主要分为五个大块
- 推荐页面
- 排行页面
- 歌手页面
- 搜索页面
- 播放器
- 滑动推荐
- 歌单
直接通过JsonP请求,可以获取 轮播图数据 和 推荐歌单 url:https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg
Slider组件 通过Better-Scroll实现,在Slider中实现Better-Scroll的初始化以及参数设置。 通过SetTimeout实现轮播,为了循环播放,需要将组件宽度设置为两倍的单个图片大小
通过flex实现
通过如下设置来保存标题只有两行,多出来的显示省略号
.recommend-flex{
display :flex;
flex-wrap:wrap;
justify-content:space-around;
}
flex-wrap :是否换行 justify-content : 两端对齐
flex:33% 0 0;
设置每一个宽度
父组件
overflow: hidden;
text-overflow: ellipsis;
文字
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
实现比较简单 主要介绍数据请求来源
url:https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg
直接通过JsonP请求
一个细节 在设定
width:100%
和 设定边距 的情况下,想要实现溢出显示省略号,需要设置box-sizing: border-box
和设定 内边距 ,外边距不可行,这样内容的宽度就是P标签的宽度减去内边距
url: https://u.y.qq.com/cgi-bin/musicu.fcg
两个技术点
- 类别框在滑动过程中悬停在顶部
- 侧边快捷导航栏
主要通过一个固定在顶部的块实现,这个块会在每一个类滑动到起始位置或结束位置时,跟随scroll一起滑动,在切换类型时,改变文本内容,并重新回到顶部。
需要计算每一个类的高度,以便于判断是否滑动到边界位置。
滑动位置通过监听 Better-Scroll 的 scroll 事件。
通过监听Touch事件来实现
- touchstart 记录起始位置,通过高度跳转到相应的类别
- touchmove 计算偏移量,通过偏移量跳转到相应的类别
- touchend (未使用)
-
歌曲请求 https://api.bzqll.com/music/tencent/url?key=579621905&id=${musicData.songmid}&br=192 mid
-
歌词请求 https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg mid
一个坑:项目中请求歌词的方法是写在Song对象内,但LocalStorage不会存储对象中的方法,在通过LocalStorage获取Song对象时需要重新赋上获取歌词的方法
通过vuex实现
在其它地方选择歌曲,会将歌曲列表加入vuex中,在player中接受并播放
歌曲的播放和暂停也通过vuex
vuex中的playList表示当前播放顺序,sequrenceList表示列表初始顺序
判断是否有播放列表来改变显示内容
显示播放列表时,播放列表从下方移入,player中渐显一个黑色蒙版
播放列表中的添加歌曲页面是写在player中 而不是播放列表组件
-
进度条
- touchsatrt 记录下起始位置 e.touches[0].pageX
- touchmove 记录下偏移量 并修改css
- touchend 改变歌曲播放进度
- click 单击直接跳转到相应的播放进度 e.offsetX
-
左右滑动切换
- touchsatrt 记录下起始位置 X,Y值 并置Ydirection为false表示进行左右滑动 e.touches[0].pageX pageY
- touchmove 记录下X,Y方向上的偏移量 如果水平位移小于垂直方向 置Ydirection 为true 并开始移动
- 在移动的过程中,根据移动的百分比移动歌词部分,并修改透明度
- touchend 实现自动补充位移量 补充动画时间
- 判断移动的距离是否大于10%,是的话就补充位移,不是的话就回退
表示一个触摸点,其属性包括 位置 压力
screenX: 511,
screenY: 400,//触点相对于屏幕左边沿的Y坐标
clientX: 244.37899780273438,
clientY: 189.3820037841797,//相对于可视区域
pageX: 244.37,
pageY: 189.37,//相对于HTML文档顶部,当页面有滚动的时候与clientX=Y 不等
force: 1,//压力大小,是从0.0(没有压力)到1.0(最大压力)的浮点数
identifier: 1036403715,//一次触摸动作的唯一标识符
radiusX: 37.565673828125, //能够包围用户和触摸平面的接触面的最小椭圆的水平轴(X轴)半径
radiusY: 37.565673828125,
rotationAngle: 0,//它是这样一个角度值:由radiusX 和 radiusY 描述的正方向的椭圆,需要通过顺时针旋转这个角度值,才能最精确地覆盖住用户和触摸平面的接触面
target: {} // 此次触摸事件的目标element
一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件
属性
- touches 一个 TouchList对象,表示一组触点,包含了所有当前接触触摸平面的触点的 Touch 对象,无论它们的起始于哪个 element 上
- targetTouches 触摸起始于当前事件的目标 element 上,并且仍然没有离开触摸平面的触点
触摸事件类型
-
touchStart 当用户在触摸平面上放置了一个触点时触发
-
touchEnd 当一个触点被用户从触摸平面上移除(当用户将一个手指离开触摸平面)时触发
-
touchMove 当用户在触摸平面上移动触点时触发
-
歌词
- 使用的lyric-parser
-
专辑封面旋转
.icon-rotate {
animation: rotate 20s linear infinite;
}
.icon-rotate-pause {
animation-play-state: paused;
}
safari 不允许在没有用户交互的情况下自动播放歌曲,所以通过一个Touch事件来播放歌曲。 我看了qq音乐的手机版页面,发现在点击歌曲进行播放时,播放器是直接出现在当前页面的,所以这个点击事件触发了当前页面audio的播放。 网易云的手机页面在点开一首歌之后并没有直接播放,需要用户点击播放按钮。 所以也解决这个问题,就需要重新考虑交互逻辑。
ios中一个div内部的div高度是不允许超过父容器的,否则只会显示父容器高度的内容。 为了解决在70%高的播放列表中现实100%高的添加歌曲组件,将添加歌曲组建写在了Player组件中。
this.$refs.middle.style.width = maxWidth + 'px';
this.$refs.middle.style.transition = 'width 0.4s';
需要同时设置搜索框的css
position: absolute;
right: 46px;
左右两边的图标通过 vue的transition实现 mode="out-in"
- vuex 传递 搜索页码
- props 传递 搜索内容 搜索结果
在每次搜索后将搜索结果存储到vuex LocalStorage中
通过两种方式搜索
- 输入框输入文本 为了防止每次输入文笔自动开始搜索,添加了一个搜索提示页面
- 点击搜索关键词 搜索历史 跳过搜索提示页面,直接显示搜索结果
通过传递一个searchMode值,来实现不同的功能
#复用的基本组件
主要实现列表上滑 封面上移并模糊
-
取消溢出隐藏,在列表上滑的过程中同时移动一个白色背景的div。
-
同时将封面图片以及文字上移。
-
在移动到达顶部时,停止移动
-
通过监听better-scroll的scroll事件来时事获取位移量
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
For a detailed explanation on how things work, check out the guide and docs for vue-loader.