滾動(dòng)插件:better-scroll

官網(wǎng):http://ustbhuangyi.github.io/better-scroll/doc/?q=#
GitHub:https://github.com/ustbhuangyi/better-scroll

在我們?nèi)粘5囊苿?dòng)端項(xiàng)目開發(fā)中煞额,處理滾動(dòng)列表是再常見不過(guò)的需求了况增,可以是豎向滾動(dòng)的列表偷溺,也可以是橫向的窖剑,用better-scroll可以幫助我們實(shí)現(xiàn)這個(gè)

什么是 better-scroll

better-scroll 是一個(gè)移動(dòng)端滾動(dòng)的解決方案谈飒,它是基于iscroll 的重寫涛癌,它和 iscroll的主要區(qū)別在這里翼虫。better-scroll 也很強(qiáng)大仲智,不僅可以做普通的滾動(dòng)列表饼齿,還可以做輪播圖饲漾、picker 等等。

better-scroll的滾動(dòng)原理

不少人可能用過(guò) better-scroll缕溉,出現(xiàn)最多的問(wèn)題是:

  • 我的 better-scroll初始化了考传, 但是沒(méi)法滾動(dòng)。
  • 不能滾動(dòng)是現(xiàn)象证鸥,我們得搞清楚這其中的根本原因僚楞。在這之前,我們先來(lái)看一下瀏覽器的滾動(dòng)原理:
  • 瀏覽器的滾動(dòng)條大家都會(huì)遇到枉层,當(dāng)頁(yè)面內(nèi)容的高度超過(guò)視口高度的時(shí)候泉褐,會(huì)出現(xiàn)縱向滾動(dòng)條;當(dāng)頁(yè)面內(nèi)容的寬度超過(guò)視口寬度的時(shí)候鸟蜡,會(huì)出現(xiàn)橫向滾動(dòng)條膜赃。也就是當(dāng)我們的視口展示不下內(nèi)容的時(shí)候,會(huì)通過(guò)滾動(dòng)條的方式讓用戶滾動(dòng)屏幕看到剩余的內(nèi)容揉忘。

那么對(duì)于 better-scroll 也是一樣的道理跳座,我們先來(lái)看一下 better-scroll 常見的 html 結(jié)構(gòu):

<div class="wrapper">     
  <ul class="content">     
    <li>...</li>     
    <li>...</li>     
    ...    
   </ul>
</div>

為了更加直觀,我們?cè)賮?lái)看一張圖:

  • 綠色部分為wrapper癌淮,也就是父容器躺坟,它會(huì)有固定的高度。
  • 黃色部分為content乳蓄,它是父容器的第一個(gè)子元素咪橙,它的高度會(huì)隨著內(nèi)容的大小而撐高。那么,當(dāng) content的高度不超過(guò)父容器的高度美侦,是不能滾動(dòng)的产舞,而它一旦超過(guò)了父容器的高度,我們就可以滾動(dòng)內(nèi)容區(qū)了菠剩,這就是better-scroll的滾動(dòng)原理易猫。

那么,我們?cè)趺闯跏蓟?better-scroll 呢具壮,如果是上述 html 結(jié)構(gòu)准颓,那么初始化代碼如下:

import BScroll from 'better-scroll' 
let wrapper = document.querySelector('.wrapper') 
let scroll = new BScroll(wrapper, {})

better-scroll 對(duì)外暴露了一個(gè) BScroll 的類,我們初始化只需要 new 一個(gè)類的實(shí)例即可棺妓。第一個(gè)參數(shù)就是我們 wrapper 的 DOM 對(duì)象攘已,第二個(gè)是一些配置參數(shù),具體參考 better-scroll 的文檔怜跑。

  • better-scroll的初始化時(shí)機(jī)很重要样勃,因?yàn)樗诔跏蓟臅r(shí)候,會(huì)計(jì)算父元素和子元素的高度和寬度性芬,來(lái)決定是否可以縱向和橫向滾動(dòng)峡眶。因此,我們?cè)诔跏蓟臅r(shí)候植锉,必須確保父元素和子元素的內(nèi)容已經(jīng)正確渲染了辫樱。如果子元素或者父元素 DOM 結(jié)構(gòu)發(fā)生改變的時(shí)候,必須重新調(diào)用 scroll.refresh() 方法重新計(jì)算來(lái)確保滾動(dòng)效果的正常汽煮。
  • 所以很多反饋的 better-scroll不能滾動(dòng)的原因多半是初始化 better-scroll 的時(shí)機(jī)不對(duì)搏熄,或者是當(dāng) DOM 結(jié)構(gòu)發(fā)送變化的時(shí)候并沒(méi)有重新計(jì)算 better-scroll

better-scroll 遇見 Vue

相信很多人對(duì) Vue.js 都不陌生暇赤,當(dāng) better-scroll 遇見 Vue,會(huì)擦出怎樣的火花呢宵凌?

如何在 Vue 中使用 better-scroll

我們把 better-scroll 和 Vue 做了結(jié)合鞋囊,實(shí)現(xiàn)了很多列表滾動(dòng)的效果。在 Vue 中的使用方法如下:

<template>     
  <div class="wrapper" ref="wrapper">        
    <ul class="content">             
      <li>...</li>             
      ...         
    </ul>     
  </div> 
</template> 
<script>     
import BScroll from 'better-scroll'      
export default {         
  mounted() {             
    this.$nextTick(() => {                 
      this.scroll = new Bscroll(this.$refs.wrapper, {})             
    })         
  }     
} 
</script>

Vue.js 提供了我們一個(gè)獲取 DOM 對(duì)象的接口—— vm.$refs瞎惫。在這里溜腐,我們通過(guò)了 this.$refs.wrapper 訪問(wèn)到了這個(gè) DOM 對(duì)象,并且我們?cè)?mounted 這個(gè)鉤子函數(shù)里瓜喇,this.$nextTick 的回調(diào)函數(shù)中初始化 better-scroll 挺益。因?yàn)檫@個(gè)時(shí)候,wrapper 的 DOM 已經(jīng)渲染了乘寒,我們可以正確計(jì)算它以及它內(nèi)層 content 的高度望众,以確保滾動(dòng)正常。

這里的 this.$nextTick 是一個(gè)異步函數(shù),為了確保 DOM 已經(jīng)渲染烂翰,感興趣的同學(xué)可以了解一下它的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)夯缺,底層用到了 MutationObserver 或者是 setTimeout(fn, 0)。其實(shí)我們?cè)谶@里把 this.$nextTick 替換成 setTimeout(fn, 20) 也是可以的(20 ms 是一個(gè)經(jīng)驗(yàn)值甘耿,每一個(gè) Tick 約為 17 ms)踊兜,對(duì)用戶體驗(yàn)而言都是無(wú)感知的。

異步數(shù)據(jù)的處理

在我們的實(shí)際工作中佳恬,列表的數(shù)據(jù)往往都是異步獲取的捏境,因此我們初始化 better-scroll的時(shí)機(jī)需要在數(shù)據(jù)獲取后,代碼如下:

<template> 
  <div class="wrapper" ref="wrapper"> 
    <ul class="content"> 
      <li v-for="item in data">{{item}}</li> 
    </ul> 
  </div> 
</template> 
<script> 
import BScroll from 'better-scroll' 
export default { 
  data() { 
    return { 
      data: [] 
    } 
  }, 
  created() { 
    requestData().then((res) => { 
      this.data = res.data 
      this.$nextTick(() => { 
        this.scroll = new Bscroll(this.$refs.wrapper, {}) 
      }) 
    }) 
  } 
} 
</script>

這里的 requestData 是偽代碼毁葱,作用就是發(fā)起一個(gè) http 請(qǐng)求從服務(wù)端獲取數(shù)據(jù)垫言,并且這個(gè)函數(shù)返回的是一個(gè) promise(實(shí)際項(xiàng)目中我們可能會(huì)用 axios 或者 vue-resource)。我們獲取到數(shù)據(jù)的后头谜,需要通過(guò)異步的方式再去初始化 better-scroll骏掀,因?yàn)?Vue 是數(shù)據(jù)驅(qū)動(dòng)的, Vue 數(shù)據(jù)發(fā)生變化(this.data = res.data)到頁(yè)面重新渲染是一個(gè)異步的過(guò)程柱告,我們的初始化時(shí)機(jī)是要在 DOM 重新渲染后截驮,所以這里用到了 this.$nextTick,當(dāng)然替換成 setTimeout(fn, 20) 也是可以的际度。

為什么這里在 created 這個(gè)鉤子函數(shù)里請(qǐng)求數(shù)據(jù)而不是放到 mounted 的鉤子函數(shù)里葵袭?因?yàn)?requestData 是發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求,這是一個(gè)異步過(guò)程乖菱,當(dāng)拿到響應(yīng)數(shù)據(jù)的時(shí)候坡锡,Vue 的 DOM 早就已經(jīng)渲染好了,但是數(shù)據(jù)改變 —> DOM 重新渲染仍然是一個(gè)異步過(guò)程窒所,所以即使在我們拿到數(shù)據(jù)后鹉勒,也要異步初始化 better-scroll。

數(shù)據(jù)的動(dòng)態(tài)更新

我們?cè)趯?shí)際開發(fā)中吵取,除了數(shù)據(jù)異步獲取禽额,還有一些場(chǎng)景可以動(dòng)態(tài)更新列表中的數(shù)據(jù),比如常見的下拉加載皮官,上拉刷新等脯倒。比如我們用 better-scroll 配合 Vue 實(shí)現(xiàn)下拉加載功能,代碼如下:

<template>
  <div class="wrapper" ref="wrapper">
    <ul class="content">
      <li v-for="item in data">{{item}}</li>
    </ul>
  <div class="loading-wrapper"></div>
</div>
</template>
<script> 
import BScroll from 'better-scroll'
export default {
  data() {
    return {data: []}
  }, 
  created() {
    this.loadData()
  }, 
  methods: {
    loadData() {
      requestData().then((res) => {
        this.data = res.data.concat(this.data)
        this.$nextTick(() => {
          if (!this.scroll) {
            this.scroll = new Bscroll(this.$refs.wrapper, {})
            this.scroll.on('touchend', (pos) => {
              // 下拉動(dòng)作
              if (pos.y > 50) {
                this.loadData()
              }
            })
          } else {
            this.scroll.refresh()
          }
        })
      })
    }
  }
} 
</script>

這段代碼比之前稍微復(fù)雜一些, 當(dāng)我們?cè)诨瑒?dòng)列表松開手指時(shí)候捺氢, better-scroll 會(huì)對(duì)外派發(fā)一個(gè) touchend 事件藻丢,我們監(jiān)聽了這個(gè)事件,并且判斷了 pos.y > 50(我們把這個(gè)行為定義成一次下拉的動(dòng)作)摄乒。如果是下拉的話我們會(huì)重新請(qǐng)求數(shù)據(jù)悠反,并且把新的數(shù)據(jù)和之前的 data 做一次 concat残黑,也就更新了列表的數(shù)據(jù),那么數(shù)據(jù)的改變就會(huì)映射到 DOM 的變化问慎。需要注意的一點(diǎn)萍摊,這里我們對(duì) this.scroll 做了判斷,如果沒(méi)有初始化過(guò)我們會(huì)通過(guò) new BScroll 初始化如叼,并且綁定一些事件冰木,否則我們會(huì)調(diào)用 this.scroll.refresh 方法重新計(jì)算,來(lái)確保滾動(dòng)效果的正常笼恰。

這里踊沸,我們就通過(guò) better-scroll 配合 Vue,實(shí)現(xiàn)了列表的下拉刷新功能社证,上拉加載也是類似的套路逼龟,一切看上去都是 ok 的。但是追葡,我們發(fā)現(xiàn)這里寫了大量命令式的代碼(這一點(diǎn)不是 Vue.js 推薦的)腺律,如果有很多類似滾動(dòng)的組件,我們就需要寫很多類似的命令式且重復(fù)性的代碼宜肉,而且我們把數(shù)據(jù)請(qǐng)求和 better-scroll 也做了強(qiáng)耦合匀钧,這些對(duì)于一個(gè)追求編程逼格的人來(lái)說(shuō),就不 ok 了谬返。

scroll 組件的抽象和封裝

因此之斯,我們有強(qiáng)烈的需求抽象出來(lái)一個(gè) scroll 組件,類似小程序的 scroll-view 組件遣铝,方便開發(fā)者的使用佑刷。

首先,我們要考慮的是 scroll 組件本質(zhì)上就是一個(gè)可以滾動(dòng)的列表組件酿炸,至于列表的 DOM 結(jié)構(gòu)瘫絮,只需要滿足 better-scroll 的 DOM 結(jié)構(gòu)規(guī)范即可,具體用什么標(biāo)簽填硕,有哪些輔助節(jié)點(diǎn)(比如下拉刷新上拉加載的 loading 層)檀何,這些都不是 scroll 組件需要關(guān)心的。因此廷支, scroll 組件的 DOM 結(jié)構(gòu)十分簡(jiǎn)單,如下所示:

<template>    
  <div ref="wrapper">        
    <slot></slot>    
  </div>
</template>

這里我們用到了 Vue 的特殊元素—— slot 插槽栓辜,它可以滿足我們靈活定制列表 DOM 結(jié)構(gòu)的需求恋拍。接下來(lái)我們來(lái)看看 JS 部分:

<script type="text/ecmascript-6">
  import BScroll from 'better-scroll'
  export default {
    props: {
        /*1 滾動(dòng)的時(shí)候會(huì)派發(fā)scroll事件,會(huì)截流藕甩。
         2 滾動(dòng)的時(shí)候?qū)崟r(shí)派發(fā)scroll事件施敢,不會(huì)截流周荐。
         3 除了實(shí)時(shí)派發(fā)scroll事件,在swipe的情況下仍然能實(shí)時(shí)派發(fā)scroll事件     
        */
      probeType: {
        type: Number,
        default: 1
      },
      //  點(diǎn)擊列表是否派發(fā)click事件
      click: {
        type: Boolean,
        default: true
      },
      //  是否開啟橫向滾動(dòng)
      scrollX: {
        type: Boolean,
        default: false
      },
      //  是否派發(fā)滾動(dòng)事件
      listenScroll: {
        type: Boolean,
        default: false
      },
      //  列表的數(shù)據(jù)
      data: {
        type: Array,
        default: null
      },
      /** * 是否派發(fā)滾動(dòng)到底部的事件僵娃,用于上拉加載 */
      pullup: {
        type: Boolean,
        default: false
      },
      /** * 是否派發(fā)頂部下拉的事件概作,用于下拉刷新 */
      pulldown: {
        type: Boolean,
        default: false
      },
      /** * 是否派發(fā)列表滾動(dòng)開始的事件 */
      beforeScroll: {
        type: Boolean,
        default: false
      },
      /** * 當(dāng)數(shù)據(jù)更新后,刷新scroll的延時(shí)默怨。 */
      refreshDelay: {
        type: Number,
        default: 20
      }
    },
    mounted() {
      // 保證在DOM渲染完畢后初始化better-scroll
      setTimeout(() => {
        this._initScroll()
      }, 20)
    },
    methods: {
      _initScroll() {
        if (!this.$refs.wrapper) {
          return
        }
        // better-scroll的初始化
        this.scroll = new BScroll(this.$refs.wrapper, {
          probeType: this.probeType,
          click: this.click,
          scrollX: this.scrollX
        })
        // 是否派發(fā)滾動(dòng)事件
        if (this.listenScroll) {
          let me = this
          this.scroll.on('scroll', (pos) => {
            me.$emit('scroll', pos)
          })
        }
        // 是否派發(fā)滾動(dòng)到底部事件讯榕,用于上拉加載
        if (this.pullup) {
          this.scroll.on('scrollEnd', () => { // 滾動(dòng)到底部
            if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
              this.$emit('scrollToEnd')
            }
          })
        }
        // 是否派發(fā)頂部下拉事件,用于下拉刷新
        if (this.pulldown) {
          this.scroll.on('touchend', (pos) => { // 下拉動(dòng)作
            if (pos.y > 50) {
              this.$emit('pulldown')
            }
          })
        }
        // 是否派發(fā)列表滾動(dòng)開始的事件
        if (this.beforeScroll) {
          this.scroll.on('beforeScrollStart', () => {
            this.$emit('beforeScroll')
          })
        }
      },
      disable() {
        // 代理better-scroll的disable方法
        this.scroll && this.scroll.disable()
      },
      enable() {
        // 代理better-scroll的enable方法
        this.scroll && this.scroll.enable()
      },
      refresh() {
        // 代理better-scroll的refresh方法
        this.scroll && this.scroll.refresh()
      },
      scrollTo() {
        // 代理better-scroll的scrollTo方法
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
      },
      scrollToElement() {
        // 代理better-scroll的scrollToElement方法
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      }
    },
    watch: {
      // 監(jiān)聽數(shù)據(jù)的變化匙睹,延時(shí)refreshDelay時(shí)間后調(diào)用refresh方法重新計(jì)算愚屁,保證滾動(dòng)效果正常
      data() {
        setTimeout(() => {
          this.refresh()
        }, this.refreshDelay)
      }
    }
  }
</script>

JS 部分實(shí)際上就是對(duì) better-scroll 做一層 Vue 的封裝,通過(guò) props 的形式痕檬,把一些對(duì) better-scroll 定制化的控制權(quán)交給父組件霎槐;通過(guò) methods 暴露的一些方法對(duì) better-scroll 的方法做一層代理;通過(guò) watch 傳入的 data梦谜,當(dāng) data 發(fā)生改變的時(shí)候丘跌,在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用 refresh 方法重新計(jì)算 better-scroll 確保滾動(dòng)效果正常,這里之所以要有一個(gè) refreshDelay 的設(shè)置是考慮到如果我們對(duì)列表操作用到了 transition-group 做動(dòng)畫效果唁桩,那么 DOM 的渲染完畢時(shí)間就是在動(dòng)畫完成之后闭树。

有了這一層 scroll 組件的封裝,我們來(lái)修改剛剛最復(fù)雜的代碼(假設(shè)我們已經(jīng)全局注冊(cè)了 scroll 組件)朵夏。

<template>
  <scroll class="wrapper" :data="data" :pulldown="pulldown" @pulldown="loadData">
    <ul class="content">
      <li v-for="item in data">{{item}}</li>
    </ul>
  <div class="loading-wrapper"></div>
</scroll>
</template>
<script> 
import BScroll from 'better-scroll'
export default {
  data() {
    return {
      data: [],
      pulldown: true
    }
  },
  created() {
    this.loadData()
  },
  methods: {
    loadData() {
      requestData().then((res) => {
        this.data = res.data.concat(this.data)
      })
    }
  }
}
</script>

可以很明顯的看到我們的 JS 部分精簡(jiǎn)了非常多的代碼蔼啦,沒(méi)有對(duì) better-scroll 再做命令式的操作了,同時(shí)把數(shù)據(jù)請(qǐng)求和 better-scroll 也做了剝離仰猖,父組件只需要把數(shù)據(jù) data 通過(guò) prop 傳給 scroll 組件捏肢,就可以保證 scroll 組件的滾動(dòng)效果。同時(shí)饥侵,如果想實(shí)現(xiàn)下拉刷新的功能鸵赫,只需要通過(guò) prop 把 pulldown 設(shè)置為 true,并且監(jiān)聽 pulldown 的事件去做一些數(shù)據(jù)獲取并更新的動(dòng)作即可躏升,整個(gè)邏輯也是非常清晰的辩棒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市膨疏,隨后出現(xiàn)的幾起案子一睁,更是在濱河造成了極大的恐慌,老刑警劉巖佃却,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件者吁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡饲帅,警方通過(guò)查閱死者的電腦和手機(jī)复凳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門瘤泪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人育八,你說(shuō)我怎么就攤上這事对途。” “怎么了髓棋?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵实檀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我仲锄,道長(zhǎng)劲妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任儒喊,我火速辦了婚禮镣奋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怀愧。我一直安慰自己侨颈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布芯义。 她就那樣靜靜地躺著哈垢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扛拨。 梳的紋絲不亂的頭發(fā)上耘分,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音绑警,去河邊找鬼求泰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛计盒,可吹牛的內(nèi)容都是我干的渴频。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼北启,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼卜朗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起咕村,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤场钉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后懈涛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惹悄,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年肩钠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泣港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡价匠,死狀恐怖当纱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踩窖,我是刑警寧澤坡氯,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站洋腮,受9級(jí)特大地震影響箫柳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啥供,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一悯恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伙狐,春花似錦涮毫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至唉侄,卻和暖如春咒吐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背属划。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工恬叹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榴嗅。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓妄呕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嗽测。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绪励,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348