1. 歌手详情页子路由配置
singer-detail组建的开发:创建并编写简单的页面
在index.js中重新配置路由,点击歌手就可以转到详情列表了
{
path: '/singer',
component: Singer,
children: [
{
path: ':id',
component: SingerDetail
}
]
}
配置好子路由之后,要有一个routerview去承载这个子路由,我们回到singer组件,在list-view(歌手列表组件)的下边挂载子router-view
<template>
<div class="singer">
<!-- 将singers数据命名为data变量传递给listview.vue组件 -->
<list-view @select="selectSinger" :data="singers"></list-view>
<!-- 承载子路由SingerDetail -->
<router-view></router-view>
</div>
</template>
添加路由跳转逻辑,我们希望我们点击歌手列表的时候,可以进行路由跳转,歌手列表基于list-view实现,所以在listview组件中,添加一个点击事件selectItem();
<ul>
<!-- 这里面的每一个li代表每个组别里面的每一位歌手 -->
<li @click="selectItem(item)" v-for="(item, index) in group.items" :key="index" class="list-group-item">
<img class="avatar" v-lazy="item.avatar">
<span class="name"></span>
</li>
</ul>
在listview中添加点击事件并将item派发出去,点击事件没有业务逻辑,仅仅是将数据传出去
selectItem(item) {
this.$emit('select', item) // 将点击的是哪一位歌手派发出去
},
回到singer.vue界面监听函数
<!-- 将singers数据命名为data变量传递给listview.vue组件 -->
<list-view @select="selectSinger" :data="singers"></list-view>
methods中实现监听函数
// 点击某个歌手
selectSinger(singer) {
this.$router.push({
path: `/singer/${singer.id}` // router的编程式跳转接口
})
console.log(`${singer.id}`)
},
点击歌手出现歌手详情,但是界面的跳转略显生硬,我们为singer-detail的跳转增加一个transition动画
<template>
<transition name="slide">
<music-list :songs="songs" :title="title" :bg-image="bgImage"></music-list>
</transition>
</template>
.slide-enter-active, .slide-leave-active
transition all 0.3s
.slide-enter, .slide-leave-to
transform translate3d(100%, 0, 0)
2. Vuex 初始化及歌手数据的配置
当我们点击歌手列表切换到歌手详情页的时候,需要把歌手的数据,歌手的名称,图片等,通过参数的传递也是可以解决的,利用vuex解决这个问题也可。
vuex官方文档
State:所有组件的所有状态和数据,放在同一的内存空间来管理。
Getter:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Mutation:你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
Action:Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
使用场景:解决多个组件之间的状态共享,这些组件可能是一些关联度很低的组件,所以我们想要共享数据就比较困难。比如遇到一些路由跳转场景,如果我们传递的参数很复杂的话,vuex是很好的选择
index: vuex的入口函数src->store->index.js
state: 状态管理:src->store->state.js
mutations: 建立mutations.js,Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
mutation-types: 使用常量替代 Mutation 事件类型
actions:异步修改,或者对mutation做一些封装
getters: 对state数据进行映射
首先,定义state.js
const state = {
singer: {},
playing: false // 播放状态
}
export default state
mutation-type:定义常量
// 定义mutation的常量
export const SET_SINGER = 'SET_SINGER'
然后在mutations.js中定义修改的操作:
import * as types from './mutation-types'
const mutations = { // mutation相关的修改方法
[types.SET_SINGER](state, singer) { // 当前状态树的state,提交mutation时传的payload
state.singer = singer
}
}
有了mutation修改数据,我们怎么映射到state中去呢,通常从getters中取state数据到vue components中
export const singer = state => state.singer // 使用getter取到组件里的数据
action.js暂时不用初始化,因为还没有相关action的修改 接下来将入口js,即index.js进行初始化
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import state from './state'
import mutations from './mutations'
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex) // 注册插件
// 调试工具
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({ // 我们要去export store的一个实例,单例模式
actions,
getters,
state,
mutations,
strict: debug, // 检测state的修改是不是来源于mutation
plugins: debug ? [createLogger()] : [] // 通过mutation修改state的时候会在控制台打印logger
})
在main.js中修改,将store注入到vue中,此时,vuex的初始化配置完成
import store from './store'
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
回到singer.vue界面:当singer.vue组件进行跳转的时候,我没去去set这个singer,我们把这个singer当成vuex的一个数据
import {mapMutations} from 'vuex'
mapMutation是对mutation进行了一层封装,我们import Mapmutation之后,我们只要在methods函数的最后,通过扩展运算符的方式调用mapMutations,去做一个对象的映射,将mutation的修改映射成一个方法名,setSinger,他实际上就是mutation-types中的SET_SINGER常量
...mapMutations({
setSinger: 'SET_SINGER'
})
之后再代码中,我们就可以调用setSinger方法往state中写数据了
// 点击某个歌手
selectSinger(singer) {
this.$router.push({
path: `/singer/${singer.id}` // router的编程式跳转接口
})
// 在setSinger中将数据(singer)传进来
// 实现了对一个mutation的提交,修改了state,实际上执行了mutations.js中的types.SET_SINGER函数
// state.singer = singer,实现了对state的修改,这里是往state中写了singer数据
this.setSinger(singer)
},
将singer数据提交到state之后,然后在详情界面singer-detail.vue中去获取数据
import {mapGetters} from 'vuex' // vuex为取出数据提供的语法糖
在computed中执行mapGetters函数
computed: { // getter映射的就是computed
...mapGetters([
// export const singer = state => state.singer // 使用getter取到组件里的数据
'singer' // 对应到store中的getters定义的singer
])
}
做了这一层映射以后,我们就相当于在vue实例中挂载了一个叫singer的属性,然后我们就可以拿到singer,在created的时候进行consloe.log()进行测试
created() {
console.log(this.singer) // 测试输出singer数据
}

3. 歌手详情数据抓取
歌手详情接口来源
singermid不同,歌手详情数据就不同,其他的都一样
// src->api>singer.js
// 歌手详情数据抓取
export function getSingerDetail(singerId) {
const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'
const data = Object.assign({}, commonParams, {
loginUin: 0,
hostUin: 0,
// format: 'jsonp',
inCharset: 'utf8',
notice: 0,
platform: 'yqq',
needNewCode: 0,
singermid: singerId,
order: 'listen',
begin: 0,
num: 100,
songstatus: 1
})
return jsonp(url, data, options)
}
回到src->common->singer-detail.vue中,在created中定义获取数据的方法
import {getSingerDetail} from 'api/singer'
import {ERR_OK} from 'api/config'
created() {
// console.log(this.singer) // 测试输出singer数据
// 调用方法获取数据
this._getDetail()
}
在_getDetail()中调用刚才的api方法getSingerDetail,之前我们已经通过…mapGetter()获取到了singer,这里讲singer的id传入getSingerDetail函数;找不到歌手id的时候,刷新会退回到singer路由
_getDetail() {
// 用户在歌手详情页面上刷新 则回退到上一页面
if (!this.singer.id) {
this.$router.push('/singer')
return
}
// 因为我们已经获取到了singer数据,此处可以通过this.singer直接调用获取歌手的id
getSingerDetail(this.singer.id).then((res) => {
if (res.code === ERR_OK) {
// console.log(res.data.list)
this.songs = this._normalizeSongs(res.data.list)
// console.log(this.songs)
}
})
},

4. 歌手详情数据处理和Song类的封装
_normalizeSongs函数是对歌手数据进行格式化处理:跟之前的singer类中进行new singer处理相同,在这里,我们创建一个song类,对song的相关属性进行封装。参数很多的场景就可以设计成类,没有设计成对象,是因为类的扩展性要比对象好很多。
创建common->js->song.js,类似于singer的id,name,avatar
// 我们需要在 _getSingerList 获取到的数据里面去提取我们需要的部分,来构造成我们需要的数据对象
export default class Song {
constructor({id, mid, singer, name, album, duration, image, url}) {
this.id = id
this.mid = mid
this.name = name
this.album = album
this.duration = duration
this.image = image
this.url = url
this.singer = singer
}
在singerDetail里面我们去维护一个data,return一个song
data() {
return {
songs: []
}
},
singer.js 获取vkey
export function getVkey(songmid) {
const url = 'https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg'
const data = Object.assign({}, {
loginUin: 0,
hostUin: 0,
platform: 'yqq',
uin: 0,
needNewCode: 0,
cid: 205361747,
songmid: songmid,
filename: `C400${songmid}.m4a`,
guid: 5544337966
})
return jsonp(url, data)
}
createSong(musicData, vkey)) // 将获取到的musicData源数据转化为我们定义好的song类,直接new的方式代码量较多,编写一个工厂方法代替,所以在js->song.js中
export function createSong(musicData, vkey) {
return new Song({
id: musicData.songid,
mid: musicData.songmid,
singer: filterSinger(musicData.singer), // 处理一首歌有两个歌手的情况
name: musicData.songname,
album: musicData.albumname,
duration: musicData.interval, // 歌曲的时长
image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${musicData.albummid}.jpg?max_age=2592000`,
url: `http://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/C400${musicData.songmid}.m4a?vkey=${vkey}&guid=5544337966&uin=0&fromtag=38`
})
}
singer展示的是一个歌手,如果有两个的话就用/来区分两个及以上的歌手,在元数据中singer是一个数组,这不是我们想要的格式,我们希望数据可以直接应用到DOM上,所以对singer数据进行过滤
/**
* 要将数组中的歌手名 拼接成一个字符串 薛之谦/欧阳娜娜
* @param {array} singer 歌手的情况{id:xx,name: xx }
*/
function filterSinger(singer) {
let ret = []
// 如果传入的歌手为空
if (!singer) {
return ''
}
singer.forEach((s) => {
ret.push(s.name)
})
return ret.join('/')
}
回到singer-detail.vue引入createSong,在_normalizeSongs中对歌手详情list数据做一个规范化处理
_normalizeSongs(list) {
let ret = []
list.forEach((item) => {
let {musicData} = item // 对象解构赋值,只取musicData数据
getVkey(musicData.songmid).then((res) => {
const vkey = res.data.items[0].vkey
if (musicData.songid && musicData.albumid) {
ret.push(createSong(musicData, vkey))
}
})
})
return ret
}
这样,ret数组中所有的Song对象,都是我们对源数据进行处理后提取的我们需要的数据格式了,之后将_normalizeSongs函数应用到getSingerDetail函数中,输出之后,数据就是我们想要的格式了
_getDetail() {
// 用户在歌手详情页面上刷新 则回退到上一页面
if (!this.singer.id) {
this.$router.push('/singer')
return
}
// 因为我们已经获取到了singer数据,此处可以通过this.singer直接调用获取歌手的id
getSingerDetail(this.singer.id).then((res) => {
if (res.code === ERR_OK) {
// console.log(res.data.list)
this.songs = this._normalizeSongs(res.data.list)
console.log(this.songs)
}
})
},
.png)
created() {
// console.log(this.singer) // 测试输出singer数据
// 调用方法获取数据
this._getDetail()
}
5. music-list 组件开发
歌手的详情页的歌单部分和热门歌单推荐详情页歌单部分是类似的,所以我们将其抽象成一个musiclist组件,通过props传入不同的数据来实现不同的歌单列表
获取完数据并进行规范化处理之后,编写music-list.vue组件,写好主体框架,
<template>
<div class="music-list">
<div class="back" @click="back">
<i class="icon-back"></i>
</div>
<h1 class="title" v-html="title"></h1>
<div class="bg-image" :style="bgStyle" ref="bgImage">
<div class="filter" ref="filter"></div>
</div>
</div>
</template>
music-list.vue组件在props中接受父组件传递过来的数据:
props: {
bgImage: {
type: String,
default: ''
},
songs: {
type: Array,
default: () => []
},
title: {
type: String,
default: ''
}
},
将其引入到父组件singer-detail.vue中,singer-detail.vue没有其他dom结构了,只需要调用music-list组件就成,父组件singer-detail主要负责给子组件music-list传递数据即可,不仅要忘了引用注册组件
import MusicList from 'components/music-list/music-list'
components: {
MusicList
}
<template>
<transition name="slide">
<!-- <div class="singer-detail"></div> -->
<music-list :bg-image="bgImage" :title="title" :songs="songs"></music-list>
</transition>
</template>
在singer-detail.vue中,songs直接将当当前的songs传入即可,通过计算属性拿到title和bgImage的值,singer在mapGetter中得到
data() {
return {
songs: []
}
},
computed: {
title() {
return this.singer.name
},
bgImage() {
return this.singer.avatar
},
// 把 `this.singer` 映射为 `this.$store.getters.singer`
...mapGetters([
'singer'
])
},
回到list-music.vue处理拿到的数据,填充DOM 显示歌手名:
<h1 class="title" v-html="title"></h1>
显示歌手图片:
<div class="bg-image" :style="bgStyle">
bgStyle可以通过一个计算属性得到
computed: {
bgStyle() {
return `background-image:url(${this.bgImage})`
}
},
这样我们就将图片加载到了组件的上方,,图片下边的歌单样式都是复用的,可以复用,抽象成song-list组件
为了方便复用歌曲列表,将列表抽象成一个基础组件src->base->song-list->song-list.vue
首先在props中定义要接受的数据
props: { // 需要接收的数据
songs: {
type: Array,
default: () => []
}
},
拿到基础数据之后去填充DOM,基础框架如下
<template>
<div class="song-list">
<ul>
<li @click="selectItem(song, index)" v-for="(song, index) in songs" :key="song.id" class="item">
<div class="content">
<h2 class="name"></h2>
<p class="desc"></p>
</div>
</li>
</ul>
</div>
</template>
然后去获得歌曲的描述: getDesc()
methods: {
getDesc(song) {
return `${song.singer}---${song.album}`
}
}
歌曲列表组件song-list.vue开发完成,我们回到music-list(song.vue+背景)中去应用这个组件。
music-list.vue中引入到子组件song-list.vue,并引入scroll.vue。为控制内部样式,在song-list.vue的外边添加一个wrapper,即song-list-wrapper。将songs作为data传入scroll中,在scroll组件中规定只有data中含有数据时才会滚动,并将songs的值传递给song-list组件
music-list.vue中引入子组件:
import Scroll from 'base/scroll/scroll'
import SongList from 'base/song-list/song-list'
music-list.vue中注册子组件:
components: {
Scroll,
SongList
}
<scroll @scroll="scroll" :probe-type="probeType" :listen-scroll="listenScroll" :data="songs" class="list" ref="list">
<div class="song-list-wrapper">
<song-list @select="selectItem" :songs="songs"></song-list>
</div>
<div class="loading-container" v-show="!songs.length">
<loading></loading>
</div>
</scroll>
之后,指定歌手的歌曲列表实现并可以滚动,但是高度不对,歌手的图片消失了,所以在music-list.vue组件中,为scroll添加一个ref,拿到sroll的DOM,为其添加一个top值,为图片腾出空间(因为不同的设备背景图的高度是不一样的)
<div class="bg-image" :style="bgStyle" ref="bgImage">
<scroll :data="songs" class="list" ref="list">
我们在mounted的生命周期下控制它的top值
mounted() {
this.imageHeight = this.$refs.bgImage.clientHeight
this.minTranslateY = -this.imageHeight + RESERVED_HEIGHT
// list是一个component对象,要通过$el取得它的DOM,将scoll的高度设置为背景图的高度
this.$refs.list.$el.style.top = `${this.imageHeight}px`
},
我们希望列表向上滑动的时候,上部图片可以向上移动
1)先将music-list的overflow属性去掉,这样歌曲列表(song-list.vue)就可以滚动上去盖住图像部分了
.list
overflow hidden // 去掉
2)这时需要一个位于歌单列表下方的图层(bg-layer),当歌单列表向上滑动的时候filter也跟着向上滑动,盖住下方的歌手图片
<!-- 位于歌单列表下方的图层 -->
<div class="bg-layer" ref="layer"></div>
<scroll @scroll="scroll" :probe-type="probeType" :listen-scroll="listenScroll" :data="songs" class="list" ref="list">
<div class="song-list-wrapper">
接下来我们要实现的是scroll层(song-list.vue)滚动的时候,bg-layer层也跟着向上滚动,所以我们要监听scroll层,也就是song-list层滚动的位置
首先在music-list中设置scroll开启监听,之后scroll就会将pos派发出来,然后再用scroll监听事件,获取自定义参数scrollY的值
created() {
this.probeType = 3
this.listenScroll = true // 监听滚动
},
然后将变量传到scroll中,设置监听方法scroll,因为只要listenScroll为ture,scroll中就会派发函数,music-list中就可以监听到scroll的pos
<scroll @scroll="scroll" :probe-type="probeType" :listen-scroll="listenScroll" :data="songs" class="list" ref="list">
在scroll中initScroll的时候已经定义好了派发函数
// 如果监听了滚动事件,在初始化列BScroll之后要派发一个监听事件
if (this.listenScroll) {
// BScroll 中的this是默认指向scroll的,所以要在me中保留vue实例的this
let me = this
// 当我触发滚动事件的时候就向父组件listview.vue,music-list.vue派发一个名为scroll的事件,还带有参数pos
this.scroll.on('scroll', (pos) => {
me.$emit('scroll', pos)
})
}
这样,我们只需要在music-list.vue组件中编写监听函数,获得scrollY的位置即可
data() {
return {
scrollY: 0
}
},
methods: {
scroll(pos) {
this.scrollY = pos.y // 实时拿到scrollY的值
},
拿到scrollY的值之后就可以设置bg-layer的偏移量
监听scrollY的变化,scrollY发生变化时,bg-layer层跟着滚动scrollY的位置
watch: {
scrollY(newY) {
this.$refs.layer.style['transform'] = `translate3D(0, ${newY}px, 0)`
this.$refs.layer.style['webkitTransform'] = `translate3D(0, ${newY}px, 0)`
}
}
bg-layer层高度是100%的,当by-layer滚动高度的超过屏幕高度时,图片又会露出来。bg-layer应该滚动到一定位置就停止,所以要设置一下他的最大滚动距离,在mounted中设置一个最小滚动距离,在watch的时候当scrollY超过最小滚动距离时就不动了
首先在mounted中记录一下最大滚动高度imageHeight
const RESERVED_HEIGHT = 40
mounted() {
// 记录一下bg-layer的最大滚动位置
this.imageHeight = this.$refs.bgImage.clientHeight
// 最小可以滚动到的位置,这是一个负值,并为顶部留出了一个text的空间
this.minTranslateY = -this.imageHeight + RESERVED_HEIGHT
// list是一个component对象,要通过$el取得它的DOM,将scoll的高度设置为背景图的高度
this.$refs.list.$el.style.top = `${this.imageHeight}px`
},
然后在watch中观测到scrollY值变化时就就设置layer层跟随滚动到一定位置
watch: {
scrollY(newY) {
// 当newY大于最小滚动值的时候就把高度设为最小滚动值,因为他们都是负值,所以取最大的值,其绝对值最小
let translateY = Math.max(this.minTranslateY, newY)
this.$refs.layer.style[transform] = `translate3d(0, ${translateY}px, 0)`
this.$refs.layer.style['webkitTransform'] = `translate3D(0, ${translateY}px, 0)`
我们希望文字在达到预留text窗口时隐藏,也就是歌单的名字不会盖住歌手的名字和返回按钮。
原来歌手bg-image图片的布局也是relative布局的,我们可以为其设置一个zIndex=10。但是又会出现一个问题,就是图片部分完全盖住了歌单列表,也就是bg-layer滚动到最大距离并且还没有盖住歌手名字之前,我们改变其z-index和高度,没有滚动到最大高度时和之前一样就行。
原来的图片布局是靠padding-top来支撑的
.bg-image
position: relative
width: 100%
height: 0
padding-top: 70%
所以在watch scrollY的时候,我们对图片的高度和index做一个限定
let zIndex = 0
if (newY < this.minTranslateY) {
// 滚动到顶时
zIndex = 10
this.$refs.bgImage.style.paddingTop = 0 // 这两句直接将高度写死就成
this.$refs.bgImage.style.height = `${RESERVED_HEIGHT}px` // 预留一个title空间
} else {
// 还没滚动到最高点时要把它重置回去,因为列表拉上去在拉下来的话图片部分就没有了,还是只露出上边一小部分
this.$refs.bgImage.style.paddingTop = '70%'
this.$refs.bgImage.style.height = 0
}
this.$refs.bgImage.style.zIndex = zIndex // if中为10,else中就不变还是为0
拉动歌曲列表的时候,实现图片的一个放大缩小(scale)的效果和模糊度的效果(blur)
首先watch scrollY的时候,先定义一个scale为1
向下拉的时候,我们希望有一个高斯模糊的效果,往上滑的时候越高它的模糊度越大,定义一个blur
let zIndex = 0
let scale = 1
let blur = 0
const percent = Math.abs(newY / this.imageHeight)
if (newY > 0) { // 向下拉的时候
scale = 1 + percent // 相当于加了一个nweY的高度
// 向下拉的时候图片被盖住一部分,设置其index大于歌名列表
zIndex = 10
} else {
blur = Math.min(2 * percent, 20)
}
this.$refs.filter.style[backdrop-filter] = `blur(${blur}px)` // 模糊
this.$refs.filter.style[webkitBackdrop-filter] = `blur(${blur}px)`
this.$refs.bgImage.style[transform] = `scale(${scale})`
this.$refs.bgImage.style[webkitTransform] = `scale(${scale})`
对transition等css属性进行封装,在common/js/dom.js中进行扩展,以兼容浏览器
// 创建了一个div的style
let elementStyle = document.createElement('div').style
// 立即执行函数,返回一个浏览器类型
let vendor = (() => {
// 各个浏览器厂商的前缀
let transformNames = {
webkit: 'webkitTransform',
Moz: 'MozTransform',
O: 'OTransform',
ms: 'msTransform',
standard: 'transform'
}
for (const key in transformNames) {
if (elementStyle[transformNames[key]] !== undefined) {
return key
}
}
return false // 所有的类型都不支持的返回false
})()
/**
* css3属性添加前缀
* @export
* @param {any} style 样式
* @returns 前缀+style
*/
export function prefixStyle(style) {
if (vendor === false) {
return false
}
if (vendor === 'standard') {
return style
}
// 首字母大写再加上剩余部分 例如:webkitTransform
return vendor + style.charAt(0).toUpperCase() + style.substr(1)
}
所以在music-list中引入:
import {prefixStyle} from 'common/js/dom'
const transform = prefixStyle('transform')
const backdrop = prefixStyle('backdrop-filter')
这样,就可以替换css style中的一下兼容性代码了(webkit-transform),他可以自动根据我们浏览器的兼容情况自动添加前缀
制作返回按钮
<div class="back" @click="back">
<i class="icon-back"></i>
</div>
methods: {
back() {
this.$router.back()
},
添加随机播放的按钮,在bg-image层中添加,添加逻辑v-show,保证歌曲列表渲染完成才会出现随机播放按钮
<div class="bg-image" :style="bgStyle" ref="bgImage">
<div class="play-wrapper">
<div class="play" v-show="songs.length > 0" ref="playBtn" >
<i class="icon-play">
<span class="text">随机播放全部</span>
</i>
</div>
按钮的出现应该在数据渲染完成之后,这里我们为按钮添加一个v-show的条件,如上所示 v-show=”songs.length > 0”
play-wrapper是一个绝对定位,当我们滚动列表的时候,按钮也会滚动到页面的顶部,滚动到顶部的时候,我们实际上是修改了bg-image的高度
if (newY < this.minTranslateY) {
// 滚动到顶时
zIndex = 10
this.$refs.bgImage.style.paddingTop = 0 // 这两句直接将高度写死就成
this.$refs.bgImage.style.height = `${RESERVED_HEIGHT}px` // 预留一个title空间
this.$refs.playBtn.style.display = 'none' // 随机播放全部按钮消失
} else {
// 还没滚动到最高点时要把它重置回去,因为列表拉上去在拉下来的话图片部分就没有了,还是只露出上边一小部分
this.$refs.bgImage.style.paddingTop = '70%'
this.$refs.bgImage.style.height = 0
this.$refs.playBtn.style.display = '' // 随机播放全部按钮显示出来
}
在scroll的最后添加loading组件,不要忘了import和components
<div class="loading-container" v-show="!songs.length">
<loading></loading>
</div>
引入、注册Loading组件
import Loading from 'base/loading/loading'
components: {
Scroll,
SongList,
Loading
}