该项目来源HuangYi, 之前写过他的eleApp,后来发现这个进阶项目-音乐 App,于是有了这个仓库
数据来源 QQ 音乐 JSONP
,Node
代理部分接口
- 基础组件:确认对话框,loading,进度条,搜索框,滚动组件
- 业务组件:歌单详情,播放内核,搜索页面,顶部导航,排行榜等组件
- 上拉刷新,函数节流搜索,消除点击延迟
- /recommand 推荐页:轮播图,推荐歌单列表
- /singer 歌手页列表:歌手列表展示,左右联动,快速入口,类似通讯录
- /singer/:id 歌手详情页:局部滚动,视差,播放音乐,音乐播放器有的功能都有
- /rank/:id 排行页:同上
- /search 搜索:搜索结果,搜索历史,热门搜索,结果播放
- 播放列表页:当前播放队列
- 用户中心页
animation-play-state: paused ios
不支持解决方案vue@2.5+
ios 不能播放问题
# 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
用于解决移动端300ms点击延迟的问题,用法:main.js中引入使用:
import fastclick from 'fastclick'
fastclick.attach(document.body)
在build文件夹下修改webpack.base.conf.js下的alias:
alias: {
// code
'common': resolve('src/common'),
'components': resolve('src/components')
}
在样式中引用要加波浪号:~import '~common/stylus/mixin'
import router from './router/index'
/* eslint-disable no-new */
new Vue({
el: '#app',
router, // 不能用大写,会报错.??
render: h => h(App)
})
@click.stop="clickEvent"
router-link跳转后,点击的链接会激活一个名为.router-link-active
的类,可借此进行css设计
把字符串符串作为 URI 组件进行编码,忽略ASCII字母数字和标点- _ . ! ~ * ' ( )
审查元素-network-JS或XHR-含fcg的项目-URL从开头至.fcg结束
包裹router-view,可将dom缓存在内存中,不会每次切换都加载
5开头都是服务端的错误
1.x版本不能无缝滚动,降到0.1.15就ok,暂不知原因
scroll组件的外层元素宽高应依据浏览器窗口进行定位(默认被内部元素撑开则无法滚动):
.recommend-content
positon fixed
top 88px
left 0
right 0
bottom 0
创建和refreshBS对象时,使用this.$nextTick
包裹,可预防因异步加载导致的BS高度计算误差而引起的滚不到底的情况(用settimeout时)
设定后,用户不能选中文本
- 安装:
cnpm i vue-lazyload --save -dev
- main.js中引入并使用:
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad, {
loading: require('common/image/ff.png') // 替代预览的图片
})
- 组件中调用:
<img v-lazy="item.url"> // 原为:src="item.url"
用v-show="!discList.length"
而非v-show="!discList"
,因data中定义了discList,即使是无元素,也不是undefined
可直接通过形如this.$refs.listview.refresh()
调用
this.$router.push({
path: `/singer/${singer.id}`
})
div {
animation-play-state: paused;
-webkit-animation-play-state: paused; /* Safari 和 Chrome */
} // 动画的css代码要同时赋予
// viewbox 视口的区域 0,0到100,100
<svg :width="radius" :height="radius" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
// r:半径 cx/cy:圆心坐标
<circle class="progress-background" r="50" cx="50" cy="50" fill="transparent" />
// stoke-dasharray:描边距离(总长) stroke-dashoffset:描边偏移(显示长度)
<circle class="progress-bar" r="50" cx="50" cy="50" fill="transparent" stroke-dasharray="100" />
</svg>
let arr = [1, 2, 3]
let newArr = arr // 这样赋值不能创建新的数组,只是对原数组的引用
for(let i = 0 ; i < newArr.length ; i++) {
newArr[i] += 1
}
console.log(newArr) // 2,3,4
console.log(arr) // 2,3,4
创建数组副本,用let _arr = arr.slice()
使用this.$ref.xxx
时,若取的对象是组件,而想取对应的根元素,就加个$el,即如this.$refs.xxx.$el.style.bottom = '60px'
let {songlist, topinfo} = res // 解构: res有很多属性,提取特定属性,赋给其他变量
let ret = {topinfo, songlist}
ios系统上测试时发现mini播放器播放图标的内外圈不对齐,将外容器添加font-size: 0
解决
降级vue版本 vue
和vue-template-complier
写死到2.3.3(package.json里去掉^),再npm i
在搜索页新增路由,而不是跳到歌手路由的子路由,这样主页的动画和歌手动画就不会干扰了
不要在mutations之外修改state数据,如直接将state内的数组赋值再修改,应在赋值语句末尾添加slice()
以返回新的数组
原因: getlyric插件的seek(跳转)方法自带播放歌词
export function debounce(func, delay) {
let timer
return function (...args) {
if (timer) {
console.log(timer)
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
- scroll设props,初始化方法里添加监听beforeScrollStart,并派发beforeScroll事件到suggest组件:
if (this.beforeScroll) {
this.scroll.on('beforeScrollStart', () => {
this.$emit('beforeScroll')
})
}
- suggest组件将beforeScroll事件传给search组件
<scroll @beforeScroll="listScroll"></scroll>
listScroll() {
this.$emit('listScroll')
},
- search组件中触发search-box的input的blur事件(失去焦点以隐藏手机键盘)
<suggest @listScroll="blurInput">
blurInput() {
this.$refs.searchBox.blur()
},
在index.html头部添加<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
transform叠加法:
// img:专辑图片 container:img的容器
_cdAnimation(container, img) {
const cTransform = getComputedStyle(container).transform
const iTransform = getComputedStyle(img).transform
container.style[transform] = cTransform === 'none'
? iTransform
: iTransform.concat(' ', cTransform)
},
- 安装good-storage插件
- 读与写:
import storage from 'good-storage'
const SEARCH_KEY = '__search__'
const SEARCH_MAX_LENGTH = 15
// compare:findindex传入的是function,所以不能直接传val
function insertArray(arr, val, compare, maxLen) {
const index = arr.findIndex(compare)
if (index === 0) {
return
}
if (index > 0) {
arr.splice(index, 1)
}
arr.unshift(val) // 插入到数组最前
if (maxLen && arr.length > maxLen) {
arr.pop() // 删除末位元素
}
}
// 存储搜索历史
export function saveSearch(query) {
let searches = storage.get(SEARCH_KEY, [])
insertArray(searches, query, (item) => {
return item === query
}, SEARCH_MAX_LENGTH)
storage.set(SEARCH_KEY, searches)
return searches
}
// 加载本地缓存的搜索历史
export function loadSearch() {
return storage.get(SEARCH_KEY, [])
}
在取得真实数据后,从接口复制到本地的json文件,在api请求时then后是真实数据,catch中从本地json中获取模拟数据:
_getDiscList() {
getDiscList()
.then((res) => {
if (res.code === ERR_OK) {
this.discList = res.data.list
}
})
.catch(() => {
console.log('没,没有推荐,开始请求模拟数据...')
setTimeout(() => {
this.discList = require('../../../static/recommend.json').data.list
}, 2000)
})
},
<div v-if="currentLyric">
<p ref="lyricLine" class="text" :class="{'current': currentLineNum === index}" v-for="(line, index) in currentLyric.lines" :key="line.id">
{{line.txt}}
</p>
</div>
<div v-else> // v-else和v-if并列 else不用写条件
<p class="text">暂无歌词</p>
</div>
报错原因是json文件的应用方式有误,我遇到的问题原因是本地json文件格式有误,写成了xxx: {xxx}
,应该是{xxx}
在pc端没异常,模拟手机调试也没问题,但在手机上测试时,rank-detail显示不全,会被tab和header遮住,几经探究发现:是div.rank的overflow:hidden
引起的,rank-detail组件是div.rank的子元素,所以多出的部分会被隐藏,显示出来的效果就是看起来header和tab把rank-detail遮住了,并不是开始预想的z-index问题.
解决方法:将div.rank的overflow属性去掉,类比没有出现问题的singer组件,将scroll组件再往下一个dom级别引入,overflow:hidden也往下写一个级别,使其和rank-detail的musci-list同级,这样就不会被遮住了,移动端测试也正常~!
webpack.dev.conf.js
中创建接口:
// 开头调用:
var express = require('express')
var axios = require('axios')
var app = express()
var apiRoutes = express.Router()
app.use('/api', apiRoutes)
// devServer的最后添加:
before(app) {
app.get('/api/getDiscList', function (req, res) {
var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' // 原api
axios.get(url, {
headers: {
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query
}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
})
}
- api的js文件中,将url换成步骤1中自定义的接口,通过axios获取返回数据
import jsonp from 'common/js/jsonp'
import {commonParams, options} from './config'
import axios from 'axios'
export function getDiscList() {
const url = '/api/getDiscList'
const data = Object.assign({}, commonParams, {
platform: 'yqq', // 加引号
hostUin: 0,
sin: 0,
ein: 29,
sortId: 5,
needNewCode: 0,
categoryId: 10000000,
rnd: Math.random(),
format: 'json'
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}
- 组件中通过api的js文件中的方法获取数据
import {getDiscList} from 'api/recommend'
_getDiscList() {
getDiscList().then((res) => {
if (res.code === ERR_OK) {
console.log('推荐:', res)
this.discList = res.data.list
} else {
console.log('没,没有推荐')
}
})
}