1. 程式人生 > >【音樂App】—— Vue-music 專案學習筆記:歌單及排行榜開發

【音樂App】—— Vue-music 專案學習筆記:歌單及排行榜開發

前言:以下內容均為學習慕課網高階實戰課程的實踐爬坑筆記。

專案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中:
  1. 新增路由容器
    <router-view></router-view>
  2. 給歌單列表新增點選事件
    @click="selectItem(item)"
  3. 定義selectItem()實現路由跳轉
    selectItem(item) {
        this.$router.push({
            path: `/recommend/${item.dissid}`
       })
    }
  • 使用vuex傳遞歌單資料
  1. state.js中:新增歌單物件
    disc: {}
  2. mutation-type.js中:定義type常量
    export const SET_DISC = 'SET_DISC'
  3. mutation.js中:建立更改函式
    [types.SET_DISC](state, disc){
        state.disc = disc
    }
  4. 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中對資料進行一些處理
  1. 同歌手詳情頁,歌曲的播放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物件

  2. 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實現路由資料通訊

  • 路由的跳轉
  1. router->index.js中:設定Rank的二級路由
    import TopList from '@/components/top-list/top-list' 
    
    {
        path: '/rank',
        component: Rank,
        children: [
            {
                  path: ':id',
                  component: TopList
             }
        ]
    }
  2. rank.vue中:新增路由容器,給列表新增點選事件,觸發路由的跳轉

    <router-view></router-view>
    @click="selectItem(item)"
    selectItem(item) {
        this.$router.push({
             path: `/rank/${item.id}`
        })
    }
  • Vuex傳遞資料
  1. state.js中:定義資料
    topList: {}
  2. mutation-types.js中:定義type常量
    export const SET_TOP_LIST = 'SET_TOP_LIST'
  3. mutations.js中:新增修改topList的方法
    [types.SET_TOP_LIST](state, topList){
         state.topList = topList
    }
  4. getters.js中:新增topList的資料對映
    export const topList = state => state.topList
  • 寫入和獲取state資料
  1. rank.vue中:通過mapMutations寫入資料
    import {mapMutations} from 'vuex'
    
    this.setTopList(item)
    
    ...mapMutations({
        setTopList: 'SET_TOP_LIST'
    })
  2. top-list.vue中:通過mapGatters拿到資料
    import {mapGetters} from 'vuex'
    
    computed: {
        ...mapGetters([
            'topList'
       ])
    }

    剩下的就是獲取資料用computed,設定資料用methods

       榜單詳情歌曲資料的抓取

  • 需求:榜單本身的背景圖不好看,想要抓取榜單詳情歌曲的圖片作為背景圖
  • 問題:原請求是Ajax請求,不能跨域,但也支援jsonp請求,只是沒有格式化不方便檢視資料
  • 解決: 線上網站www.bejson.com 格式化為json檔案
  • 因為這塊資料QQ音樂也做了加密,這裡抓取資料同歌單詳情頁方式
  1. 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)
    }
  2. 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)
        }
    })
  3. 獲得資料後,在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
    }
  1. 傳入<song-list>
    <song-list :rank="rank"></song-list>
  2. 這樣就把最外層父元件傳入的rank應用到了song-list.vue元件中
  3. top-list.vue中維護資料rank為true,同時傳入<music-list>中

 注:專案來自慕課網