前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记。
项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star。
![]() |
![]() |
歌单及详情页 | 排行榜及详情页 |
一、歌单详情页开发 |
歌单详情页布局介绍及Vuex实现路由数据通讯
- components->disc目录下:创建disc.vue
- router->index.js中:给Recommend添加二级子路由Disc
{ path: '/recommend', component: Recommend, children: [ { path: ':id', component: Disc } ] }
- recommend.vue中:
- 添加路由容器
<router-view></router-view>
- 给歌单列表添加点击事件
@click="selectItem(item)"
- 定义selectItem()实现路由跳转
selectItem(item) { this.$router.push({ path: `/recommend/${item.dissid}` }) }
- 使用vuex传递歌单数据
- state.js中:添加歌单对象
disc: {}
- mutation-type.js中:定义type常量
export const SET_DISC = 'SET_DISC'
- mutation.js中:创建更改函数
[types.SET_DISC](state, disc){ state.disc = disc }
- getters.js中:定义数据映射
export const disc = state => state.disc
- recommend.vue中:使用mapMutations修改disc
import {mapMutations} from 'vuex' ...mapMutations({ setDisc: 'SET_DISC' }) selectItem(item) { this.$router.push({ path: `/recommend/${item.dissid}` }) this.setDisc(item) //更改state中的disc }
- disc.vue中:通过mapGetters获取到date中的disc
import {mapGetters} from 'vuex computed: { ...mapGetters([ 'disc' ]) }
- 传递数据:获取数据在computed里面,设置数据在methods里面
歌单详情页数据抓取
- api->recommend.js中:添加getSonglist接口
export function getSongList (disstid) { const url = '/api/getSongList' const data = Object.assign({}, commonParams, { uin: 0, format: 'json', notice: 0, needNewCode: 1, new_format: 1, pic: 500, disstid, //关键数据 type: 1, json: 1, utf8: 1, onlysong: 0, picmid: 1, nosign: 1, song_begin: 0, platform: 'h5', song_num: 100, _: +new Date() }) return axios.get(url, { params: data }).then((res) => { return Promise.resolve(res.data) }) }
- 因为数据也经过加密,在webpack.dev.config.js中模仿header:
app.get('/api/getSongList', function (req, res) { var url = 'https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg' axios.get(url, { headers: { referer: 'https://y.qq.com/', host: 'c.y.qq.com' }, params: req.query }).then((response) => { res.json(response.data) }).catch((e) => { console.log(e) }) })
注意:后端配置之后,一定要重新启动!!!
歌单详情页数据的处理和应用
- 调用getSongList()方法获取数据,在created()时获取
import {getSongList} from '@/api/recommend' _getSongList () { if(!this.disc.dissid){ //在歌单详情页强制刷新后,即没有获得id时,回退到推荐页面 this.$router.push('/recommend') return } getSongList(this.disc.dissid).then((res) => { if (res.code === ERR_OK) { this.songs = this._normalizeSongs((res.cdlist[0].songlist)) // console.log(res) // console.log(res.cdlist[0].songlist) // console.log(this._normalizeSongs(res.cdlist[0].songlist)) } }) }
- 获得数据后,在methods中对数据进行一些处理
- 同歌手详情页,歌曲的播放url中的vkey需要发送请求获取,同时将处理好的数据封装新的Song实例
import {ERR_OK} from '@/api/config' import {creatSongList} from '@/common/js/song' import {getMusic} from '@/api/singer' _normalizeSongs (list) { let ret = [] list.forEach((musicData) => { if (musicData.id && musicData.album) { // ret.push(creatSongList(musicData)) getMusic(musicData.mid).then((res) => { // console.log(res) if(res.code === ERR_OK){ // console.log(res.data) const svkey = res.data.items const songVkey = svkey[0].vkey const newSong = creatSongList(musicData, songVkey) ret.push(newSong) } }) } }) return ret }
其中:调用getMusic()获取播放地址的vkey,调用creatSongList()实例化Song对象
- common->js->song.js中: 创建creatSongList()
export function creatSongList (musicData, songVkey) { return new Song({ id: musicData.id, mid: musicData.mid, singer: filterSinger(musicData.singer), name: musicData.name, album: musicData.albumname, duration: musicData.interval, image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${musicData.album.mid}.jpg?max_age=2592000`, //注意guid以实时数据为主 url: `http://ws.stream.qqmusic.qq.com/C400${musicData.mid}.m4a?vkey=${songVkey}&guid=6319873028&uin=0&fromtag=66` }) }
二、排行榜及详情页开发 |
排行榜数据抓取
- 在api目录下:创建rank.js添加获取数据的接口
import jsonp from '@/common/js/jsonp' import {commonParams, options} from './config' export function getTopList() { const url = "https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg" const data = Object.assign({}, commonParams, { platform: 'h5', needNewcode: 1 }) return jsonp(url, data, options) }
- rank.vue中:调用getTopList()方法获取数据,在created()时获取
import {getTopList} from '@/api/rank' import {ERR_OK} from '@/api/config' created() { this._getTopList() }, methods: { _getTopList() { getTopList().then((res) => { if(res.code === ERR_OK){ console.log(res.data.topList) } }) } }
排行榜数据应用
- 在data中维护数据
topList: []
- 获取数据后赋值
this.topList = res.data.topList
- 将数据填入DOM:应用scroll组件替换父元素<div>并传入topList数据实现滚动
<scroll class="top-list" :data="topList">
- 优化:应用loading组件,设置当topList没有内容时显示loading
- 优化:应用mixin,实现播放器底部适配
榜单详情页布局及Vuex实现路由数据通讯
- 路由的跳转
- router->index.js中:设置Rank的二级路由
import TopList from '@/components/top-list/top-list' { path: '/rank', component: Rank, children: [ { path: ':id', component: TopList } ] }
-
rank.vue中:添加路由容器,给列表添加点击事件,触发路由的跳转
<router-view></router-view> @click="selectItem(item)"
selectItem(item) { this.$router.push({ path: `/rank/${item.id}` }) }
- Vuex传递数据
- state.js中:定义数据
topList: {}
- mutation-types.js中:定义type常量
export const SET_TOP_LIST = 'SET_TOP_LIST'
- mutations.js中:添加修改topList的方法
[types.SET_TOP_LIST](state, topList){ state.topList = topList }
- getters.js中:添加topList的数据映射
export const topList = state => state.topList
- 写入和获取state数据
- rank.vue中:通过mapMutations写入数据
import {mapMutations} from 'vuex' this.setTopList(item) ...mapMutations({ setTopList: 'SET_TOP_LIST' })
- top-list.vue中:通过mapGatters拿到数据
import {mapGetters} from 'vuex' computed: { ...mapGetters([ 'topList' ]) }
剩下的就是获取数据用computed,设置数据用methods
榜单详情歌曲数据的抓取
- 需求:榜单本身的背景图不好看,想要抓取榜单详情歌曲的图片作为背景图
- 问题:原请求是Ajax请求,不能跨域,但也支持jsonp请求,只是没有格式化不方便查看数据
- 解决: 在线网站www.bejson.com 中格式化为json文件
- 因为这块数据QQ音乐也做了加密,这里抓取数据同歌单详情页方式
- api->rank.js中:添加获取数据的接口
export function getMusicList(topid) { const url = "https://c.y.qq.com/v8/fcg-bin/fcg_v8_toplist_cp.fcg" const data = Object.assign({}, commonParams, { page: 'detail', type: 'top', tpl: 3, platform: 'h5', needNewcode: 1 }) return jsonp(url, data, options) }
- top-list.vue中:调用getMusicList()方法获取数据,在created()时获取
import {getMusicList} from '@/api/rank' import {ERR_OK} from '@/api/config' created() { this._getMusicList() } getMusicList(this.topList.id).then((res) => { if(res.code === ERR_OK){ this.songs = this._normalizeSongs(res.songlist) // console.log(res.songlist) } })
- 获得数据后,在methods中对数据进行一些处理:同歌单详情页,歌曲的播放url中的vkey需要发送请求获取,同时将处理好的数据封装新的Song实例
import {getMusic} from '@/api/singer' import {createSong} from '@/common/js/song' _normalizeSongs(list) { let ret = [] list.forEach((item) => { const musicData = item.data // console.log(musicData) if (musicData.songid && musicData.albumid) { getMusic(musicData.songmid).then((res) => { // console.log(res) if(res.code === ERR_OK){ // console.log(res.data) const svkey = res.data.items const songVkey = svkey[0].vkey const newSong = createSong(musicData, songVkey) ret.push(newSong) } }) } }) return ret }
这样数据就获取到了,剩下的把songs的数据传递给子组件就可以了
- 把bgImage的图片换成榜单第一首歌的图片
bgImage() { if (this.songs.length) { return this.songs[0].image } return this.topList.picUrl }
带排行的song-list组件的扩展和应用
- 添加rank的DOM结构、图片以及样式
- 添加props字段rank,默认false,设置只有当rank为true时排行显示
rank: { type: Boolean, default: false }
<div class="rank" v-show="rank">
- 定义两个方法分别获得动态绑定的class和文案
<span :class="getRankCls(index)">{{getRankText(index)}}</span>
getRankCls(index) { if(index<=2) { return `icon icon${index}` }else{ return 'text' } }, getRankText(index) { if(index > 2) { return index + 1 } }
- music-list.vue作为中间组件,也需要扩展一个props字段rank
rank: { type: Boolean, default: false }
- 传入<song-list>
<song-list :rank="rank"></song-list>
- 这样就把最外层父组件传入的rank应用到了song-list.vue组件中
- top-list.vue中维护数据rank为true,同时传入<music-list>中
注:项目来自慕课网