亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

vue-music(1)音樂(lè)播發(fā)器 項(xiàng)目開(kāi)發(fā)記錄

happen / 2063人閱讀

摘要:在中新建組件許文瑞正在吃屎。。。。在中添加如下代碼三歌手組件開(kāi)發(fā)歌手首頁(yè)開(kāi)發(fā)數(shù)據(jù)獲取數(shù)據(jù)獲取依舊從音樂(lè)官網(wǎng)獲取歌手接口創(chuàng)建我們和以前一樣,利用我們封裝的等發(fā)放,來(lái)請(qǐng)求我們的接口,返回給。

Vue-Music
跟學(xué)一個(gè)網(wǎng)課老師做的仿原生音樂(lè)APP跟學(xué)的筆記,記錄點(diǎn)滴,也希望對(duì)學(xué)習(xí)vue初學(xué)小伙伴有點(diǎn)幫助

一| 前期工作 1.項(xiàng)目初始化

npm install -g vue-cli

vue init webpack vue-music

npm install stylus stylus-loader -D

修改eslint.js

修改webpack.base.conf.js resolve配置項(xiàng)簡(jiǎn)化路徑

2.裝包

npm install fastclick --save 取消默認(rèn)300ms延遲

import fastClick from "fastclick"
fastClick.attach(document.body)

npm install babel-polyfill

對(duì)es6的高級(jí)語(yǔ)法進(jìn)行轉(zhuǎn)義當(dāng)運(yùn)行環(huán)境中并沒(méi)有實(shí)現(xiàn)的一些方法,babel-polyfill 會(huì)給其做兼容
需要在main.js中引入

npm install babel-runtime --save 輔助編譯 不需要引入即可用

babel-runtime 是供編譯模塊復(fù)用工具函數(shù)。是錦上添花
babel-polyfil是雪中送炭,是轉(zhuǎn)譯沒(méi)有的api.
二| 頂部tab導(dǎo)航 && Recommend 頁(yè)面組件開(kāi)發(fā) 1. 頂部導(dǎo)航欄 tab

建立基本的頁(yè)面骨架,基本的組件引入
header rank recommend search singer tab 這幾個(gè)組件組成頁(yè)面骨架

2. recommend組件

數(shù)據(jù)獲取

qq音樂(lè)

Jsonp
Jsonp發(fā)送的不是一個(gè)ajax請(qǐng)求,他動(dòng)態(tài)創(chuàng)建一個(gè)script標(biāo)簽,script沒(méi)有同源策略限制,所以能跨域  有一個(gè)返回參數(shù) callback , 后端解析url,返回一個(gè)方法。

安裝: npm install jsonp@0.2.1

jsonp github倉(cāng)庫(kù)

以后需要多出引用jsonp跨域請(qǐng)求,將其創(chuàng)建在 scr/common/jsonp.js

jsonp promise化
import originJSONP from "jsonp"
export default function jsonp(url, data, option) {
  // jsonp的三個(gè)參數(shù)
  // - url-->一個(gè)純凈的url地址
  // - data --> url中的 query 通過(guò) data 拼到url上
  // - option
  url += (url.indexOf("?") < 0 ? "?" : "&") + param(data)
  return new Promise((resolve, reject) => {
    originJSONP(url, option, (err, data) => {
      if (!err) {
        resolve(data)
      } else {
        reject(err)
      }
    })
  })
}
 // 拼接data到url
function param (data) {
  let url = ""
  for (var k in data) {
    let value = data[k] !== undefined ? data[k] : ""
    url += `&${k}=${encodeURIComponent(value)}`
  }
  // encodeURIComponent() 函數(shù)可把字符串作為 URI 組件進(jìn)行編碼。
  return url ? url.substring(1) : ""
}
注意:當(dāng)路徑報(bào)錯(cuò)的時(shí)候,我們要想到webpack.base.conf.js配置文件中的 alias 選項(xiàng) 確保路徑是否匹配
Recommend的數(shù)據(jù)獲取

recommend.vue 中的 created 生命周期鉤子中調(diào)用_getRecommend 方法

_getRecommend 方法調(diào)用recommend.js中暴露出來(lái)的getRecommend方法

getRecommend 方法調(diào)用了 Jsonp 方法, Jsonp方法抓取接口,從而獲得數(shù)據(jù)

有的jsonp接口url很長(zhǎng),但是真正的url知識(shí)前面的部分

大公司一般用0來(lái)代表一切正常

輪播圖組件

輪播圖數(shù)據(jù)獲取完成后,就下來(lái)做的就是搭建輪播頁(yè)面 ,接下來(lái)編寫(xiě)一個(gè)輪播組件 slider.vue

新建base文件夾,儲(chǔ)存如同slider.vue的基礎(chǔ)組件

在silder.vue中,我們使用了slot插槽,外部引用slider的時(shí)候slider標(biāo)簽里面包裹的dom會(huì)被插入到slot插槽部分。

在recommend.vue中 引入 import Slider from "base/slider/slider",并在components中注冊(cè)Slider,之后就可以使用Slider標(biāo)簽了

將jsonp返回的slider數(shù)據(jù)存儲(chǔ)到recommend數(shù)組中,然后遍歷recommned 數(shù)組項(xiàng)循環(huán)渲染內(nèi)容

這個(gè)時(shí)候我們打開(kāi)項(xiàng)目,會(huì)發(fā)現(xiàn)已有數(shù)據(jù),但是樣式還不行,在props中添加loop,autoplay,interval(滾動(dòng)間隔),

使用了第三方輪播 better-scroll 來(lái)進(jìn)一步實(shí)現(xiàn) slider

新版的BS中snap屬性集合成了一個(gè)對(duì)象選項(xiàng) 而舊版的是多帶帶的屬性名,這點(diǎn)要注意

初始化BS,在什么時(shí)候初始化?

我們要保證渲染的時(shí)機(jī)是正確的,通常在mounted生命周期鉤子中初始化,保證BS正常渲染的話我們通常在mounted里面加一個(gè)延遲

 mounted () {
    setTimeout(() => { // 瀏覽器17ms刷新一次, 這里延遲20ms 確保組件已經(jīng)渲染完成
      this._setSliderWidth() // 設(shè)置slider寬度
      this._initDots() // 初始話dots
      this._initSlider() // 初始化slider
    }, 20)

_setSliderWidth方法 -- 輪播圖組件的寬度計(jì)算

這里要注意,這時(shí)候執(zhí)行玩寬度方法之后,可能無(wú)效,這是因?yàn)樵趯挾扔?jì)算的時(shí)候,slot插槽里面的東西還未加載,為了解決這個(gè)問(wèn)題,我們可以在recommend.vue中 給slider 的父元素 加上v-if="recommends.length",確保渲染時(shí)機(jī)正確

_initSlider()方法 -- 使用new BScroll 創(chuàng)建輪播實(shí)例,設(shè)置無(wú)限滾動(dòng)及其他的相關(guān)初始化配置,至此,我們的輪播頁(yè)面已經(jīng)可以無(wú)縫滾動(dòng)了

添加dots導(dǎo)航

五個(gè)數(shù)據(jù),dom有七個(gè),因?yàn)閘oop為ture的時(shí)候,bs會(huì)自動(dòng)在前后各拷貝一份。我們想要添加dots,必須保證和數(shù)據(jù)數(shù)一樣,所以我們應(yīng)該在bs初始化之前完成dots的初始化

初始化dots為一個(gè)長(zhǎng)度為childern.length的數(shù)組
this.dots = new Array(this.children.length)
在slider.vue中循環(huán)
v-for="(item,index) of dots"
添加選中樣式
:class="{active:currentPageIndex === index}"
在bs滾動(dòng)的時(shí)候 會(huì)派發(fā)一個(gè)事件 在初始化slider 綁定一個(gè)事件

    this.slider.on("scrollEnd", () => {
    let pageIndex = this.slider.getCurrentPage().pageX
    if (this.loop) {
      pageIndex -= 1
      this.currentPageIndex = pageIndex
      if (this.autoplay) {
        clearTimeout(this.timer)
        this._play()
      }
    }
  })

使用了 bs中的 getCurrentPage 方法來(lái)獲取滾動(dòng)的當(dāng)前頁(yè)面
在autoplay中使用了bs 的 goToPage 方法來(lái)實(shí)現(xiàn)輪播

監(jiān)聽(tīng)窗口大小改變自動(dòng)改變 && 優(yōu)化slider

之前的slider基本完成,但是此時(shí)如果改變窗口大小,頁(yè)面就會(huì)亂掉

使用resize窗口監(jiān)聽(tīng)事件,配合bs的refresh刷新方法 實(shí)現(xiàn)每一次改變窗口大小都能重置寬度

  window.addEventListener("resize", () => {
    if (!this.slider) { // slider還沒(méi)有初始化的時(shí)候
      return
    }
    this._setSliderWidth(true)
    this.slider.refresh()
  })

在app.vue 中使用keepalive標(biāo)簽,來(lái)避免重復(fù)請(qǐng)求

我們?cè)谔D(zhuǎn)到其他頁(yè)面的時(shí)候,要記得清理定時(shí)器,優(yōu)化效率

  destroyed() {
    clearTimeout(this.timer) // 性能優(yōu)化小習(xí)慣
  }
歌單組件 歌單組件數(shù)據(jù)獲取

在pc版的qq音樂(lè)中獲取請(qǐng)求接口

由于QQ音樂(lè)的歌單數(shù)據(jù)時(shí),請(qǐng)求接口host和refer規(guī)定了必須是qq音樂(lè)的地址,我們本地就會(huì)請(qǐng)求失敗。為了解決這個(gè)問(wèn)題,我們可以使用 手動(dòng)代理 偽裝成qq音樂(lè)地址請(qǐng)求接口 欺騙接口
Vue proxyTable代理 后端代理接口
在項(xiàng)目開(kāi)發(fā)的時(shí)候,接口聯(lián)調(diào)的時(shí)候一般都是同域名下,且不存在跨域的情況下進(jìn)行接口聯(lián)調(diào),但是當(dāng)我們現(xiàn)在使用vue-cli進(jìn)行項(xiàng)目打包的時(shí)候,我們?cè)诒镜貑?dòng)服務(wù)器后,比如本地開(kāi)發(fā)服務(wù)下是 http://localhost:8080 這樣的訪問(wèn)頁(yè)面,但是我們的接口地址是 http://xxxx.com/save/index 這樣的接口地址,我們這樣直接使用會(huì)存在跨域的請(qǐng)求,導(dǎo)致接口請(qǐng)求不成功,因此我們需要在打包的時(shí)候配置一下,我們進(jìn)入 config/index.js 代碼下如下配置即可:
dev: {
    // 靜態(tài)資源文件夾
    assetsSubDirectory: "static",
    // 發(fā)布路徑
    assetsPublicPath: "/",
    // 代理配置表,在這里可以配置特定的請(qǐng)求代理到對(duì)應(yīng)的API接口
    // 例如將"localhost:8080/api/xxx"代理到"www.example.com/api/xxx"
    // 使用方法:https://vuejs-templates.github.io/webpack/proxy.html
    proxyTable: {
      "/": {
        target: "https://c.y.qq.com", // 接口的域名
        secure: false, // 如果是https接口,需要配置這個(gè)參數(shù)
        changeOrigin: true, // 如果接口跨域,需要進(jìn)行這個(gè)參數(shù)配置
        pathRewrite: {
          "^/api": "/"
        },
        headers: {
          referer: "https://c.y.qq.com"
        }
      }
    }
注意: "/api" 為匹配項(xiàng),target 為被請(qǐng)求的地址,因?yàn)樵?ajax 的 url 中加了前綴 "/api",而原本的接口是沒(méi)有這個(gè)前綴的,所以需要通過(guò) pathRewrite 來(lái)重寫(xiě)地址,將前綴 "/api" 轉(zhuǎn)為 "/"。如果本身的接口地址就有 "/api" 這種通用前綴,就可以把 pathRewrite 刪掉。
表單組件開(kāi)發(fā)

我們通過(guò)代理獲得ajax數(shù)據(jù)后,將其賦值給 discList
this.discList = res.data.list
之后將disclist渲染到組件中
v-for="item of discList"

滾動(dòng)組件 Scroll.vue

由于 滾動(dòng) 是一個(gè)很基礎(chǔ)的組件 所以在common里創(chuàng)建scroll.vue組件,使代碼結(jié)構(gòu)化

在Recommend.vue中  一定要綁定data數(shù)據(jù),因?yàn)閟croll.vue中 watch 監(jiān)聽(tīng)data數(shù)據(jù)的變化來(lái)刷新better-scroll 這里的可以綁定recommend.vue中的 discList 數(shù)組來(lái)座位 data

這里的 recommends 和 discList 數(shù)據(jù)獲取是有先后順序的,一般都是先recommends再discList,如果先獲取到的是discList的話 歌單列表就會(huì)出現(xiàn)滾動(dòng)不到底部的問(wèn)題

為了確保recommend數(shù)據(jù)后加載的情況下我們的表單還能正常滾動(dòng)發(fā),我們可以給slider中的img添加一個(gè)loadImage方法@load="loadImage",方法調(diào)用一個(gè) refresh方法即可 this.$refs.scroll.refresh()
為了避免請(qǐng)求的每一張圖片都執(zhí)行一次,我們可以設(shè)置一個(gè)bool標(biāo)志位來(lái)控制 ,只要有一張圖片加載完成即可,如下:

      loadImage() {
        if (!this.checkLoaded) {
          this.$refs.scroll.refresh()
          this.checkLoaded = true
        }
      }
表單組件優(yōu)化

圖片的懶加載

節(jié)省流量,提升加載速度
npm 安裝
npm install vue-lazyload
在main.js中添加代碼
import VueLazyLoad from "vue-lazyload"
Vue.use(VueLazyLoad, {
  loading: require("common/images/touxiang.png")
})

在Recommend.vue中使用

解決圖片點(diǎn)擊失效

有些情況下點(diǎn)擊事件之間互相沖突,我們?cè)谑褂胒astclick的時(shí)候,可以給點(diǎn)擊的dom添加一個(gè)fastclick里的一個(gè)css needsclick的類(lèi)名,來(lái)確保點(diǎn)擊事件可以正常執(zhí)行

loading組件

為了增加交互體驗(yàn),在表單還未渲染之前,我們可以使用一個(gè)loading來(lái)占位。

在base中新建loading組件



在recommend.vue中添加如下代碼:

  
三| 歌手組件開(kāi)發(fā) 1.歌手首頁(yè)開(kāi)發(fā) 數(shù)據(jù)獲取

數(shù)據(jù)獲取依舊從qq音樂(lè)官網(wǎng)獲取

歌手接口

創(chuàng)建singer.js

我們和以前一樣,利用我們封裝的jsonp等發(fā)放,來(lái)請(qǐng)求我們的接口,返回給singer.vue。

成功獲取數(shù)據(jù)以后,我們發(fā)現(xiàn),官網(wǎng)的數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)和我們想要的不一樣,所以我們下一步進(jìn)行數(shù)據(jù)結(jié)構(gòu)的聚合處理
數(shù)據(jù)處理

我們希望的數(shù)據(jù)結(jié)構(gòu)是數(shù)據(jù)按照字母排序的數(shù)組再加上一個(gè)熱門(mén)的數(shù)組的集合,顯然我們?cè)诠倬W(wǎng)的到的數(shù)據(jù)不是這樣的,我們構(gòu)造一個(gè)_normalizeSinger方法來(lái)完成:

_normalizeSinger(list) { // 處理數(shù)據(jù)結(jié)構(gòu) 形參為list
      let map = { // 把數(shù)據(jù)都存在map對(duì)象中
        hot: { // 熱門(mén)城市
          title: HOT_NAME,
          items: [] // 初始化空數(shù)組
        }
      }
      list.forEach((item, index) => { // 循環(huán)數(shù)組中的每一項(xiàng)
        if (index < HOT_SINGER_LENGTH) { // 因?yàn)樵紨?shù)據(jù)是按照熱度排列的,所以獲取前十的熱門(mén)
          map.hot.items.push(new Singer({ // push到我們的hot數(shù)組中
          // new Singer: 為了模塊化和減少代碼的復(fù)用,我們?cè)赾ommon > js 創(chuàng)建了一個(gè)singer.js
          // 來(lái)創(chuàng)建一個(gè)類(lèi)構(gòu)造器 里面包括歌手頭像的拼接
            id: item.Fsinger_mid,
            name: item.Fsinger_name
          }))
        }
        const key = item.Findex // 歌手姓氏字首字母
        if (!map[key]) { // 如果不存在
          map[key] = { // 創(chuàng)建
            title: key,
            items: []
          }
        }
        map[key].items.push(new Singer({ // 追加到map.items中
          id: item.Fsinger_mid,
          name: item.Fsinger_name
        }))
      })

      // 為了得到有序列表 我們需要處理map
      let hot = [] // 熱門(mén)城市
      let ret = [] // 字母表城市
      for (let key in map) { // 循環(huán)
        let val = map[key]
        if (val.title.match(/[a-zA-Z]/)) { // 正則匹配字母
          ret.push(val)
        } else if (val.title === HOT_NAME) {
          hot.push(val) // 熱門(mén)城市
        }
      }
      ret.sort((a, b) => {
        return a.title.charCodeAt(0) - b.title.charCodeAt(0) // 把字母城市按charcode字母排序
      })

      return hot.concat(ret) // 將字母城市追加到hot城市 返回給外部
    }

細(xì)節(jié)點(diǎn)注意

關(guān)于歌手圖片的獲取,通過(guò)官網(wǎng)觀察,我們發(fā)現(xiàn)圖片是有一個(gè)網(wǎng)址拼接item.Fsinger_mid 來(lái)完成的,所以我們?cè)赾ommon >js >singer.js中 使用了${}來(lái)拼接,獲取歌手圖片地址,拼接url語(yǔ)法是使用的是 `` 而不是" "
listview.vue開(kāi)發(fā)

數(shù)據(jù)我們獲取到了,我們接下來(lái)開(kāi)發(fā)listview.vue組件,因?yàn)檫@個(gè)列表組件我們后面有很多頁(yè)面也要用到,所以我們?cè)赽ase下創(chuàng)建基礎(chǔ)組件 listview.vue

在listview.vue中引入 我們之前封裝好的scroll組件
import Scroll from "base/scroll/scroll"

通過(guò)獲取的數(shù)據(jù),進(jìn)行兩次遍歷渲染,就能得到我們想要的dom頁(yè)面了

html代碼如下

至此 歌手頁(yè)面就能正常滾動(dòng)了

shortcutList字母導(dǎo)航器

接下來(lái),開(kāi)始我們的字母導(dǎo)航器的樣式制作

我們可以在listview.vue中創(chuàng)建一個(gè)計(jì)算屬性shortcutList

computed: {
      shortcutList() {
        return this.data.map((group) => {
          return group.title.substr(0, 1)
        })
      }
    },

之后在頁(yè)面中v-for渲染shortcutList即可 配合css樣式 實(shí)現(xiàn)邊欄的字母導(dǎo)航dom的制作

    
  • {{item}}

靜態(tài)的字母導(dǎo)航在頁(yè)面中已經(jīng)展現(xiàn)出來(lái)了

接下來(lái) 來(lái)給導(dǎo)航器添加滑動(dòng)點(diǎn)擊等事件,使其動(dòng)態(tài)化

滑動(dòng)右邊字母導(dǎo)航 listview實(shí)時(shí)滾動(dòng)

在字母html標(biāo)簽中加入touch事件**

  @touchstart="onShortcutTouchStart"
  @touchmove.stop.prevent="onShortcutTouchMove"

在循環(huán)中遍歷index值,在后面的touch中獲取索引,由于蕾類(lèi)似此類(lèi)獲取數(shù)據(jù)的方法是很多地方都能用到的,我們?cè)赿om.js中添加getData方法

export function getData(el, name, val) {
  const perfix = "data-"
  name = perfix + name
  if (val) {
    return el.setAttribute(name, val)
  } else {
    return el.getAttribute(name)
  }
}

接下來(lái) 為scroll組件添加 跳轉(zhuǎn)方法

scrollTo() {
      this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
    },
    scrollToElement() {
      this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)

完整的touch方法代碼如下:

onShortcutTouchStart(e) {
      let anchorIndex = getData(e.target, "index") // 獲取data
      let firstTouch = e.touches[0] // 剛開(kāi)始觸碰的位置坐標(biāo)
      this.touch.y1 = firstTouch.pageY
      this.touch.anchorIndex = anchorIndex
      this._scrollTo(anchorIndex) // 通過(guò)使用_scrollTo方法來(lái)跳轉(zhuǎn)到我們的字母所在位置
    },
    onShortcutTouchMove(e) { // 屏幕滑動(dòng)方法 要明確開(kāi)始滾動(dòng)和結(jié)束滾動(dòng)的兩個(gè)位置,然后計(jì)算出滾動(dòng)到哪一個(gè)字母
      let firstTouch = e.touches[0] // 停止?jié)L動(dòng)時(shí)的位置坐標(biāo)
      this.touch.y2 = firstTouch.pageY // 保存到touch對(duì)象中
      let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0 // 計(jì)算滾動(dòng)了多少個(gè)字母
      let anchorIndex = parseInt(this.touch.anchorIndex) + delta // this.touch.anchorIndex 字符串轉(zhuǎn)化為整型
      this._scrollTo(anchorIndex) // 跳轉(zhuǎn)到字母位置
    }
注意:通過(guò)getData方法的到的anchorIndex是一個(gè)字符串,記得要用parseInt轉(zhuǎn)化為數(shù)字

至此 滑動(dòng)字母導(dǎo)航器 左邊的list已經(jīng)可以實(shí)現(xiàn)滾動(dòng)了

滾動(dòng)左邊list 右邊字母導(dǎo)航高亮

解決這個(gè)問(wèn)題 ,就要知道左邊listview滾動(dòng)到的相對(duì)位置

在data中增加scrollY 和 currentIndex來(lái)實(shí)時(shí)監(jiān)聽(tīng)listview滾動(dòng)的位置 和 應(yīng)該滾動(dòng)到的具體索引

在scroll標(biāo)簽組件綁定@scroll="scroll" 來(lái)將滾動(dòng)的實(shí)時(shí)位置賦值給this.scrollY

    scroll(pos) {
      this.scrollY = pos.y
      console.log(pos) // 測(cè)試
    }

在listview中添加監(jiān)視屬性data

watch: {
    data() {
      setTimeout(() => { // 數(shù)據(jù)變化到dom變化有一個(gè)延遲,所以這個(gè)加一個(gè)定時(shí)器
        this._calculateHeight() // 計(jì)算每一個(gè)group的高度
      }, 20)
    }
每次data變化,都會(huì)重新計(jì)算group的高度

_calculateHeight方法

    _calculateHeight() {
      this.listHeight = []
      const list = this.$refs.listGroup
      let height = 0
      this.listHeight.push(height)
      for (let i = 0; i < list.length; i++) {
        let item = list[i]
        height += item.clientHeight
        this.listHeight.push(height) // 得到一個(gè)包含每一個(gè)group高度的數(shù)組
      }
    }

這樣 就能得到一個(gè)包含所有g(shù)rroup高度的一個(gè)數(shù)據(jù)

在watch里監(jiān)聽(tīng)scrollY

拿到了每組的位置,我們可以監(jiān)聽(tīng)scrollY 聯(lián)合兩者判斷字母導(dǎo)航器應(yīng)該滾動(dòng)到的位置
    scrollY(newY) {
      const listHeight = this.listHeight
      // 當(dāng)滾動(dòng)到頂部 newY > 0
      if (newY > 0) {
        this.currentIndex = 0
        return
      }

      // 在中間部分滾動(dòng)
      for (let i = 0; i < listHeight.length; i++) {
        let height1 = listHeight[i]
        let height2 = listHeight[i + 1]
        if (-newY >= height1 && -newY < height2) {
          this.currentIndex = i
          this.diff = height2 + newY // 注意 newY為負(fù)值
          return
        }
      }
      // 當(dāng)滾動(dòng)到底部,且-newY 大于最后一個(gè)元素的上線
      this.currentIndex = listHeight.length - 2
    }

currentIndex 綁定類(lèi) 實(shí)現(xiàn)字母高亮

:class="{"current": currentIndex === index}"

?

細(xì)節(jié)優(yōu)化

完善_scrollTo方法

    _scrollTo(index) {
      if (!index && index !== 0) { // 點(diǎn)擊以外的部分 無(wú)反應(yīng)
        return
      }
      if (index < 0) { // 滑動(dòng)到頂部時(shí) index為負(fù)
        index = 0
      } else if (index > this.listHeight.length - 2) { // 滑動(dòng)到尾部
        index = this.listHeight.length - 2
      }
      this.scrollY = -this.listHeight[index] // 每次點(diǎn)擊都更改scrollY以實(shí)現(xiàn)同步
      this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 300)
    }

fixedTitle

計(jì)算屬性

    fixedTitle() {
      if (this.scrollY > 0) {
        return ""
      }
      return this.data[this.currentIndex] ? this.data[this.currentIndex].title : ""
    }

頁(yè)面html

    

{{this.fixedTitle}}

至此 頂部的fixedtitle標(biāo)題就做好了 但是我們發(fā)現(xiàn)兩個(gè)title在重合的時(shí)候 并不是很完美,下面我們就來(lái)添加一個(gè)頂上去的動(dòng)畫(huà)來(lái)優(yōu)化

在scrollY函數(shù)中 我們可以輕松獲取一個(gè) diff 值

this.diff = height2 + newY // 注意 newY為負(fù)值

通過(guò)監(jiān)聽(tīng)diff 我們可以來(lái)實(shí)現(xiàn)我們的要求

    diff(newVal) {
      let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0
      if (this.fixedTop === fixedTop) {
        return
      }
      this.fixedTop = fixedTop
      this.$refs.fixed.style.transform = `translate3d(0,${fixedTop}px,0)`
    }

2.歌手詳情頁(yè)
歌手詳情使用二級(jí)子路由來(lái)開(kāi)發(fā)
字路由 / 二級(jí)路由設(shè)置
路由是由組件承載的

在router -- index.js中 寫(xiě)入代碼

添加字路由

{
      path: "/singer",
      name: "Singer",
      component: Singer,
      children: [
        {
          path: ":id",
          component: SingerDetail
        }
      ]
    }

如代碼所示,在Singer component組件路由選項(xiàng)中,添加children 實(shí)現(xiàn)二級(jí)路由,然后需要在頁(yè)面上加上router-view

標(biāo)簽來(lái)掛在這個(gè)二級(jí)路由顯示頁(yè)面

編寫(xiě)跳轉(zhuǎn)邏輯

在次頁(yè)面中,二級(jí)路由的跳轉(zhuǎn)是在listview.vue中通過(guò)點(diǎn)擊事件向外派發(fā)事件來(lái)實(shí)現(xiàn)的

    selectItem(item) {
      this.$emit("select", item) // 向外派發(fā)事件
    }
因?yàn)閘istview.vue是一個(gè)基礎(chǔ)組件,不會(huì)編寫(xiě)業(yè)務(wù)邏輯,所以把點(diǎn)擊事件派發(fā)出去,讓外部實(shí)現(xiàn)業(yè)務(wù)邏輯的編寫(xiě)

在singer.vue 中,我們監(jiān)聽(tīng)到這個(gè)派發(fā)出來(lái)的select

然后在selectSinger方法里面使用vue-router的 編程式跳轉(zhuǎn)接口

 selectSinger(singer) {
      this.$router.push({
        path: `/singer/${singer.id}` // 跳轉(zhuǎn)頁(yè)面
      })
    }
添加轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

將singer-detail.vue 組件用transition標(biāo)簽包裹

并在css中添加動(dòng)畫(huà)

.slide-enter-active, .slide-leave-active
    transition: all 0.3s
 .slide-enter, .slide-leave-to
    transform: translate3d( 0, 100%, 0)

就下來(lái),開(kāi)始正式開(kāi)發(fā)singer-detail組件,在這之前,我們先了解一下Vuex 跳轉(zhuǎn)到vuex筆記

獲取singer-detail數(shù)據(jù)
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, {
    hostUin: 0,
    needNewCode: 0,
    platform: "h5page",
    order: "listen",
    begin: 0,
    num: 50,
    songstatus: 1,
    g_tk: 649509476,
    singermid: singerId // 注意是mid而不是id 不要出錯(cuò)
  })

  return jsonp(url, data, options)
}
當(dāng)在singer-detail頁(yè)面上刷新的時(shí)候,會(huì)獲取不到數(shù)據(jù),因?yàn)槲覀兊臄?shù)據(jù)是通過(guò)跳轉(zhuǎn)得到的,如果我們?cè)趕inger-detail數(shù)據(jù)上刷新,將返回上一級(jí)signer this.$router.push("/singer")
整理獲取的數(shù)據(jù)結(jié)構(gòu)

common>js>song.js

export default class Song {
  constructor({id, mid, singer, name, album, duration, image, url}) {
    this.id = id
    this.mid = mid
    this.singer = singer
    this.name = name
    this.album = album
    this.duration = duration
    this.image = image
    this.url = url
  }
}

export function createSong(musicData) {
  return new Song({
    id: musicData.songid,
    mid: musicData.songmid,
    singer: filterSonger(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://ws.stream.qqmusic.qq.com/C100${musicData.songmid}.m4a?fromtag=0&guid=126548448&crazycache=1`
  })
}

function filterSonger(singer) {
  let ret = []
  if (!singer) {
    return ""
  }
  singer.forEach((s) => {
    ret.push(s.name)
  })
  return ret.join("/")
}

通過(guò)方法調(diào)用類(lèi)構(gòu)造器,我們就能通過(guò)createSong(musicData)來(lái)整理獲得我們需要的結(jié)構(gòu)數(shù)據(jù)

singer-detail

  methods: {
    _getDetail() {
      if (!this.singer.id) {
        this.$router.push("/singer")
      }
      getSingerDetail(this.singer.id).then((res) => {
        if (res.code === ERR_OK) {
          console.log(res.data.list)
          this.songs = this._normalizeSongs(res.data.list)
        }
      })
    },
    _normalizeSongs(list) {
      let ret = []
      list.forEach((item) => {
        let {musicData} = item
        if (musicData.songid && musicData.albummid) {
          ret.push(createSong(musicData)) 
        }
      })
      return ret
    }
  }

這樣 通過(guò)調(diào)用_normalizeSongs方法 --> createSong 來(lái)得到songs數(shù)據(jù)

開(kāi)發(fā)MusicList.vue組件

在props中接受變量 bgImgae songs title

在singer-detail

通過(guò)計(jì)算屬性拿到title 和 bgImage ,

這樣就完成了父組件的singer-detail向子組件的music-list的傳值

因?yàn)楦枨斜硎菨L動(dòng)的 我們?cè)趍usic-list中復(fù)用了scroll組件

我們還需要編寫(xiě)一個(gè)song-lsit組件,為接下來(lái)所用 跳轉(zhuǎn)到song-list組件開(kāi)發(fā)

在music-list編寫(xiě)代碼:

    
      
至此,打開(kāi)頁(yè)面,我們可以看到歌單列表已經(jīng)可以正常滾動(dòng)
1. 解決圖片撐開(kāi)問(wèn)題
這是我們發(fā)現(xiàn)我們的頁(yè)面上全部被歌單列表所占用, 要計(jì)算圖片的位置把歌手背景圖展現(xiàn)出來(lái)

在mounted生命周期鉤子里添加

 this.$refs.list.$el.style.top = `${this.$refs.bgImage.clientHeight}px`

這樣就能實(shí)現(xiàn)歌手海報(bào)圖的展示了

2. 實(shí)現(xiàn)海報(bào)圖跟著滾動(dòng)的效果

我們?cè)趍usic-list.vue中加入一個(gè)layer層,用于跟著跟單一起滾動(dòng),來(lái)覆蓋我們的bg-image,這樣就能視覺(jué)上達(dá)到我們想要的效果了

監(jiān)聽(tīng)滾動(dòng)距離

為scroll組件傳入probeType值和listenScroll值

    created() {
      this.probeType = 3
      this.listenScroll = true
    }

為scroll添加scroll方法來(lái)監(jiān)聽(tīng)滾動(dòng)距離

      scroll(pos) {
        this.scrollY = pos.y
      }

并監(jiān)聽(tīng)scrollY數(shù)據(jù)

    watch: {
      scrollY(newY) {
        let translateY = Math.max(this.minTranslateY, newY)
        let zIndex = 0
        let scale = 1
        let blur = 0
        this.$refs.layer.style[transform] = `translate3d(0, ${translateY}px, 0)`
        const percent = Math.abs(newY / this.imageHeight)
        if (newY > 0) {
          scale = 1 + percent
          zIndex = 10
        } else {
          blur = Math.min(20 * percent, 20)
        }
        this.$refs.filter.style[backdrop] = `blur(${blur}px)`
        if (newY < this.minTranslateY) {
          zIndex = 10
          this.$refs.bgImage.style.paddingTop = 0
          this.$refs.bgImage.style.height = `${RESERVED_HEIGHT}px`
          this.$refs.pbtn.style.display = "none"
        } else {
          this.$refs.bgImage.style.paddingTop = "70%"
          this.$refs.bgImage.style.height = 0
          this.$refs.pbtn.style.display = ""
        }
        this.$refs.bgImage.style.zIndex = zIndex
        this.$refs.bgImage.style[transform] = `scale(${scale})`
      }
    }
3. 處理滾動(dòng)到頂部的時(shí)候歌手title被歌單覆蓋的問(wèn)題
處理方法見(jiàn)上面代碼zIndex相關(guān)操作
4. 下滑的時(shí)候bg-image圖片放大
處理見(jiàn)上代碼 bgImage scale相關(guān)的操作
5. 加入loading組件
在scroll結(jié)尾復(fù)用loading 即可
開(kāi)發(fā)song-list組件


在music-list中傳入song值

a. Vuex 什么是vuex
Vuex 是一個(gè)專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。Vuex 也集成到 Vue 的官方調(diào)試工具 devtools extension,提供了諸如零配置的 time-travel 調(diào)試、狀態(tài)快照導(dǎo)入導(dǎo)出等高級(jí)調(diào)試功能。

簡(jiǎn)單的說(shuō),當(dāng)我們的vue項(xiàng)目比較復(fù)雜的時(shí)候,有的時(shí)候兩個(gè)兄弟組件,或者相關(guān)度聯(lián)系很低的組件相互之間需要同時(shí)獲取或監(jiān)聽(tīng)同一個(gè)數(shù)據(jù)或狀態(tài),這個(gè)時(shí)候我們就要使用vuex

vuex 就像是一個(gè)大的機(jī)房,里面存著共享數(shù)據(jù)。這個(gè)房間我們可以讓任何一個(gè)組件進(jìn)來(lái)獲取數(shù)據(jù)或者更新數(shù)據(jù)

如何使用vuex

安裝vuex

npm install vuex --save

在項(xiàng)目的根目錄下,我們一般會(huì)新建一個(gè)store文件夾,里面添加新建文件:

入口文件 index.js

存放狀態(tài) state.js

存放Mutations mutations.js

存放mutations相關(guān)數(shù)據(jù)的 mutation-types.js

數(shù)據(jù)修改 執(zhí)行Mutations actions.js

數(shù)據(jù)映射 getters.js

getters 和 vue 中的 computed 類(lèi)似 , 都是用來(lái)計(jì)算 state 然后生成新的數(shù)據(jù) ( 狀態(tài) ) 的。

以此項(xiàng)目為例子,需要各個(gè)組件之間共享一個(gè)singer數(shù)據(jù)

state.js

const state = {
  singer: {}
}

export default state

mutation-types.js

export const SET_SINGER = "SET_SINGER"
使用常量替代 mutation 事件類(lèi)型在各種 Flux 實(shí)現(xiàn)中是很常見(jiàn)的模式。這樣可以使 linter 之類(lèi)的工具發(fā)揮作用,同時(shí)把這些常量放在多帶帶的文件中可以讓你的代碼合作者對(duì)整個(gè) app 包含的 mutation 一目了然

mutations.js

import * as types from "./mutation-types"
// import * as obj from "xxx" 會(huì)將 "xxx" 中所有 export 導(dǎo)出的內(nèi)容組合成一個(gè)對(duì)象返回。
const mutations = {
  [types.SET_SINGER](state, singer) {
    state.singer = singer
  }
}
export default mutations
mutations.js 可以理解為是一個(gè)修改數(shù)據(jù)的方法的集合

getter.js

有時(shí)候我們需要從 store 中的 state 中派生出一些狀態(tài),如果有多個(gè)組件需要用到此屬性,我們要么復(fù)制這個(gè)函數(shù),或者抽取到一個(gè)共享函數(shù)然后在多處導(dǎo)入它——無(wú)論哪種方式都不是很理想。

Vuex 允許我們?cè)?store 中定義“getter”(可以認(rèn)為是 store 的計(jì)算屬性)。就像計(jì)算屬性一樣,getter 的返回值會(huì)根據(jù)它的依賴被緩存起來(lái),且只有當(dāng)它的依賴值發(fā)生了改變才會(huì)被重新計(jì)算。

export const singer = state => state.singer

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) // 注冊(cè)插件

const debug = process.env.NODE_ENV !== "production" // 線下調(diào)試的時(shí)候 debug 為 ture

export default new Vuex.Store({ // new一個(gè)實(shí)例
  actions,
  getters,
  state,
  mutations,
  strict: debug, // 開(kāi)啟嚴(yán)格模式,用于下面來(lái)控制是否開(kāi)啟插件
  plugins: debug ? [createLogger()] : [] // 開(kāi)啟插件
})

main.js

在vue的main.js 中 注冊(cè) vuex

import store from "./store"
....

new Vue({
  el: "#app",
  render: h => h(App),
  router,
  store
})

以上,vuex的初始化就完成了

singer.vue 寫(xiě)入 state

在組件中提交 Mutation

你可以在組件中使用 this.$store.commit("xxx") 提交 mutation,或者使用 mapMutations 輔助函數(shù)將組件中的 methods 映射為 store.commit 調(diào)用(需要在根節(jié)點(diǎn)注入 store)。

import {mapMutations} from "vuex"

在methods結(jié)尾添加

...mapMutations({
    setSinger: "SET_SINGER" // 將 `this.setSinger()` 映射為 `this.$store.commit("SET_SINGER")`
})

通過(guò)this.setSinger(singer) 實(shí)現(xiàn)了對(duì)Mutations的提交

singer-detail.vue 取出state數(shù)據(jù)

引入

import {mapGetters} from "vuex"

在computed中

  computed: {
    ......
    
    ...mapGetters([
      "singer"  // 把 `this.signer` 映射為 `this.$store.getters.singer`
    ])
  }

至此,singer-detail 和 singer 之間就實(shí)現(xiàn) singer 的共享了

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97028.html

相關(guān)文章

  • 基于Vue.js的音樂(lè)播放(Webapp)

    摘要:概述項(xiàng)目是基于,成品是一個(gè)移動(dòng)端的音樂(lè)播放器,來(lái)源于的實(shí)戰(zhàn)課程。播放器是全局組件,放在下面,通過(guò)傳遞數(shù)據(jù),觸發(fā)提交,從而使播放器開(kāi)始工作。將請(qǐng)求的數(shù)據(jù)格式化后再通過(guò)傳遞,組件間共享,實(shí)現(xiàn)歌曲的播放切換等。 概述 項(xiàng)目是基于Vue.js,成品是一個(gè)移動(dòng)端的音樂(lè)播放器,來(lái)源于imooc的實(shí)戰(zhàn)課程。自己動(dòng)手實(shí)踐并加以修改拓展。項(xiàng)目的大致流程是Vue-cli構(gòu)建開(kāi)發(fā)環(huán)境,分析需求,設(shè)計(jì)構(gòu)思,規(guī)...

    widuu 評(píng)論0 收藏0
  • js實(shí)現(xiàn)音樂(lè)播放

      這篇文章為大家講如何用JSd代碼實(shí)現(xiàn)音樂(lè)播放。  音樂(lè)播放的主要js代碼  音樂(lè)數(shù)據(jù)的數(shù)組對(duì)象  這里不僅有前端網(wǎng)頁(yè)提供數(shù)據(jù),還有為后面的js代碼提供了音樂(lè)路徑,分享給大家。  {   ablum:"海闊天空",   artist:"Beyond",   id:1,   name:"大地",   path:"musics/1...

    3403771864 評(píng)論0 收藏0
  • 【收藏】2019年最新Vue相關(guān)精品開(kāi)源項(xiàng)目庫(kù)匯總

    摘要:前言本文的前身是源自上的項(xiàng)目但由于該項(xiàng)目上次更新時(shí)間為年月日,很多內(nèi)容早已過(guò)期或是很多近期優(yōu)秀組件未被收錄,所以小肆今天重新更新了內(nèi)容并新建項(xiàng)目。提交的項(xiàng)目格式如下項(xiàng)目名稱子標(biāo)題相關(guān)介紹如果收錄的項(xiàng)目有錯(cuò)誤,可以通過(guò)反饋給小肆。 前言 本文的前身是源自github上的項(xiàng)目awesome-github-vue,但由于該項(xiàng)目上次更新時(shí)間為2017年6月12日,很多內(nèi)容早已過(guò)期或是很多近期優(yōu)...

    williamwen1986 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<