1. 程式人生 > >【餓了麼】—— Vue2.0高仿餓了麼核心模組&移動端Web App專案爬坑(二) 【重點突破】—— 當better-scroll 遇見Vue

【餓了麼】—— Vue2.0高仿餓了麼核心模組&移動端Web App專案爬坑(二) 【重點突破】—— 當better-scroll 遇見Vue

 前言:上一篇專案總結介紹了頁面骨架的開發、header元件的開發,這一篇主要梳理:商品元件開發、商品詳情頁實現。專案github地址:https://github.com/66Web/ljq_eleme,歡迎Star。


goods
一、商品元件開發
  • App.vue主元件傳seller物件給每個路由:
<router-view :seller="seller"></router-view>

    

 兩欄佈局-flex佈局手機螢幕自適應

  • 設計:無高度滾動條,高度超過視口高度就會隱藏
<div class="goods">
     <div class="menu-wrapper"></div>
     <div class="foods-wrapper"></div>
</div>
.goods
     display: flex
     position: absolute
     top: 174px
     bottom: 46px
     width: 100%
     overflow: hidden
     .menu-wrapper
            flex: 0 0 80px
width: 80px background: #f3f5f7 .foods-wrapper flex: 1

      左側佈局-選單列表

  • 需求:文字標題可能單行,也可能多行,但都要在列表項中垂直居中
  • 小技巧:使用列表項display:table,文字標題disable:table-cell
.menu-item
    display: table
    height: 54px
    width: 56px
    line-height: 14px
    .text
    display: table-cell
width: 56px vertical-align: middle font-size: 12px
  • 關於box-sizing:border-box; 規定兩個並排的帶邊框的框

      右側佈局-食品列表

  • 列表巢狀:第一層遍歷商品項item in goods, 第二層遍歷單個商品的資訊項food in item.foods

      列表滾動-better-scroll 第三方JS庫

  1. goods.vue 引入:
    import BScroll from 'better-scroll';
  2. ref 屬性獲取dom元素:駝峰命名法 
    <div class="menu-wrapper" ref="menuWrapper">
    <div class="foods-wrapper" ref="foodsWrapper">
  3. better-scroll初始化:

    methods: {
        _initScroll(){
              this.meunScroll=new BScroll(this.$refs.menuWrapper,{});
              this.foodsScroll=new BScroll(this.$refs.foodsWrapper,{});
        }
    }
  4. 成功回撥函式中呼叫_initScroll方法:

    this.$nextTick(()=>{
         this._initScroll();
    })
  • this.$nextTick()這個方法作用是當資料被修改後使用這個方法會回撥獲取非同步更新後的dom再render出來 

  • 如果不在下面的this.$nextTick()方法裡回撥這個方法,資料改變後再來計算滾動軸就會出錯

      左右聯動

  • 需求:滾動右側,左側跟著變化;點選左側,右側滾動到相應位置
  • 原理:依賴右側滾動列表實時變化的Y值(縱座標),移動到哪個區間,左側列表就要顯示哪個區間
  • 【滾動右側時左側相應滾動】思路&實現:
  1. 在data中定義陣列用來儲存不同區間的高度
    data () {
        return {
          goods:[],
          listHeight: []
        }
    }
  2. 為了獲取高度,給food-list定義一個class--food-list-hook,不用來編寫css,專門用來獲取DOM元素,沒有實際的效果,只是用來被js選擇的

    <li v-for="item in goods" :key="item.id" class="food-list food-list-hook">
  3. 定義foodList拿到每個li,每個li是包括包括標題在內的每一類food的高度,不是單獨的一種good,將_calculateHeight放在nextTick中初始化_initScroll的後面,保證其能正確計算到高度

    _calculateHeight() {
         //food-list-hook類的新增知識為了能拿到food列表,例如,拿到的是多個類似整個粥品的區塊
        let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
        let height = 0;
        this.listHeight.push(height); //listHeight是一個遞增的區間陣列,是每個專區高度的累加
        for (let i = 0; i < foodList.length; i++) {
          let item = foodList[i];
          height += item.clientHeight;
          this.listHeight.push(height);
        }
     }
  4. 在data中定義一個scrollY物件,用來跟蹤滾動的高度 scrollY:0;在初始化betterScroll時,為右側新增probeType--可以檢測到右側實時滾動的位置,監聽scroll,將其實時滾動的位置暴露出來

    data () {
        return {
          goods:[],
          listHeight: [],
          scrollY: 0
        }
    }
    _initScroll() {
              this.meunScroll=new BScroll(this.$refs.menuWrapper,{
                click: true //使better-scroll可點選,預設派發一個點選事件
              });
              this.foodsScroll=new BScroll(this.$refs.foodsWrapper,{
                click: true,
                probeType: 3 //BScroll滾動時,能實時告訴我們滾動的位置,類似探針的效果
              });
    
               //foodsScroll監聽事件,在scroll滾動時能見位置實時暴露出來
              this.foodsScroll.on('scroll', (pos) => {
                this.scrollY = Math.abs(Math.round(pos.y));//本身是個負值,取正值
              })
     }
  5. 拿到滾動的高度和內容區的固定高度之後, 檢視scrollY落在哪個區間,並返回那個區間的索引(!height2是測試最後一個區間的)其中,>= 向下的是一個閉區間,這樣第一個就會高亮了

    computed: {
      currentIndex() { //currentIndex對應選單欄的下標
        for (let i = 0; i < this.listHeight.length; i++) { //不要忘了加this引用
          let height1 = this.listHeight[i];
          let height2 = this.listHeight[i + 1];
          //獲得了一個區間的上下範圍,判斷scrollY落到這個區間,!height2是判斷最後一個區間
          //避免i溢位,>= 向下的是一個閉區間,這樣第一個就會高亮了
          if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
            return i; //對映到第5行menu的變化
          }
        }
        return 0;
    }
  6. 拿到index之後,回到左側的menu區,當我們遍歷menu的時候,如果$index等於我們計算得到的currentIndex時,就為當前的li新增一個current樣式

    <!-- 如果index等於currentIndex,就為這個li新增一個current類,改變左側導航欄的背景顏色-->
    <li v-for="(item,index) in goods" :key="item.id"  
    class
    ="menu-item" :class="{'current': currentIndex === index}" @click = "selectMenu($index, $event)">
    &.current
         position: relative
         z-index: 10
         margin-top: -1px
         background: #ffffff
         font-weight: 700
         .text
              border-none()
  • 【點選左側右側滾動】思路&實現:
  1. 在左側選單欄新增點選事件selectMenu, @click = "selectMenu($index, $event)",將index傳進去,就可以知道點選的是哪個區域,然後利用原生DOM操作將高度滾動到相應的位置
  2. 點選左側選單欄的時候沒有反應,因為BScroll預設阻止點選事件,所以在 _initScroll()中獲取DOM物件時新增click: true,並解決PC端雙點選問題,event是點選時的event
    selectMenu (index, event) {
        if (!event._constructed) { //瀏覽器直接return掉,去掉自帶click事件的點選
           return;
        }
        let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
        let ref = foodList[index]; //取到index對應的DOM
        this.foodsScroll.scrollToElement(ref, 300);//滾動到DOM所在位置
        //console.log(index);
    }

      購物車元件

  • 定位在視口底部: fixed佈局——右側寬度固定,左側自適應
    <div class="content">
          <div class="content-left"></div>
          <div class="content-right"></div>
    </div>
    .content
         display: flex
         background: #141d27
         .content-left
              flex: 1  /*讓所有彈性盒模型物件的子元素都有相同的長度,忽略它們內部的內容*/
         .content-right
              flex: 0 0 105px /*flex三個引數依次表示:等分、內容縮放情況、站位空間*/
              width: 105p
  • display:inline-block有一個預設間隙的問題 —— 解決:父級font-size:0

  • 三種狀態轉換: 資料驅動Dom變化

      cartcontrol元件

  • 在設計尺寸基礎上增加點選區域: padding
  • 檢測資料的變化:Vue.set
  1. 引入:
    Vue import Vue from 'vue';
  2. 使用Vue.set介面:

    Vue.set(this.food, 'count', 1);
  • 按鈕平移+漸隱漸現+滾動動畫:
    <transition name="move">...</transition>
    .cart-decrease
         display: inline-block
         padding: 6px
         transform: translate3d(0, 0, 0)
         transform: rotate(0)
         &.move-enter-active, &.move-leave-active
              transition: all 0.4s linear
              transform: translate3d(0, 0, 0)
              transform: rotate(0)
         &.move-enter, &.move-leave-active
              opacity: 0
              transform: translate3d(24px, 0, 0)/*開啟硬體加速,讓動畫更流暢*/
              transform: rotate(180deg)
         .inner
              display: inline-block
              line-height: 24px
              font-size: 24px
              color: rgb(0, 160, 220)
    View Code

      購物車拋物線小球動畫

  • data資料中定義一個數組,存放5個小球,這5個小球可以滿足的動畫的執行
     data() {
             return {
                    balls: [{ //每一個成員都用來維護當前小球的狀態,初始狀態都是隱藏的
                        show: false
                    },
                    { 
                        show: false
                    },
                    {  
                        show: false
                    },
                    {  
                        show: false
                    },
                    {  
                        show: false
                    }],
                    //新增一個變數,用來存貯已經下落的小球
                    dropBalls: [],
                    fold: true //購物車詳情列表預設摺疊
              };
    }
  • 佈局
    <div class="ball-container">
        <div v-for="(ball, index) in balls" :key="index">
            <div class="ball-container">
                 <div v-for="(ball, index) in balls" :key="index">
                       <transition name="drop" >
                              <div v-show="ball.show" class="ball">
                                     <div class="inner inner-hook"></div>
                               </div>
                       </transition>
                 </div> 
             </div>
        </div> 
    </div>
  • :key報錯問題:key值是必須唯一的,如果重複就會報錯。可以把key值改為index,就可以避免這個情況
    <div v-for="(ball, index) in balls" :key="index" v-show="ball.show"></div>
  • 動畫需求:小球有拋物線軌跡運動的過渡,而且發射出去就不會再回來了
  1. 動畫屬性只用enter,不用leave,並且小球起始點需要動態計算
    .ball-container
        position: fixed
        left: 32px
        bottom: 22px
        z-index: 200
        .inner
            width: 15px
            height: 15px
            border-radius: 50%
            background-color: #00A0DC
            transition: all 1s linear
            &.drop-enter-active
                  transition: all 1s cubic-bezier(0.49, -0.29, 0.75, 0.41)
  2. CSS3 三次貝塞爾曲線(cubic-bezier):

       貝塞爾曲線通過控制曲線上的四個點(起始點、終止點以及兩個相互分離的中間點)來創造、編輯圖形,
      繪製出一條光滑曲線並以曲線的狀態來反映動畫過程中速度的變化。

      線上除錯地址: http://cubic-bezier.com/#.17,.67,.83,.67  

  3. 動畫分為兩層,外層控制小球y軸方向和運動的軌道,內層控制x軸方向的運動
  4. 使用js動畫鉤子,vue在實現動畫的時候提供了幾個javascript鉤子,可配合css動畫一起使用,也可單獨使用

     methods: {
            dropMove(el) {
            //    console.log(el)
               for(let i=0; i<this.balls.length; i++) {
                   let ball = this.balls[i];
                   if(!ball.show) {
                      ball.show = true;
                      ball.el = el;
                      this.dropBalls.push(ball);
                      return;
                   }
               }
            },
            beforeEnter(el, done) {
                let count = this.balls.length;
                while (count--) {
                    let ball = this.balls[count];
                    if(ball.show) {                    
                        let rect = ball.el.getBoundingClientRect();//返回元素的大小及其相對於視口的位置
                        let x = rect.left - 32  //ball-container left:32
                        let y = -(window.innerHeight - rect.top -22);
                        el.style.display = '';
                        el.style.transform = `translate3d(0,${y}px,0)`;//外層元素縱向移動
                        el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                        let inner = el.getElementsByClassName('inner-hook')[0];//內層元素橫向移動
                        inner.style.webkitTransform = `translate3d(${x}px, 0, 0)`;
                        inner.style.transform = `translate3d(${x}px, 0, 0)`;
                        // console.log(el);
                    }
                }
            },
            dropEnter(el, done) {
                /*手動取到offsetHeight, 觸發瀏覽器重繪*/
                let rf = el.offsetHeight;
                this.$nextTick(() => {  //樣式重置回來
                    el.style.webkitTransform = 'translate3d(0, 0, 0)'// 設定小球掉落後最終的位置
                    el.style.transform = 'translate3d(0, 0, 0)'
                    let inner = el.getElementsByClassName('inner-hook')[0]
                    inner.style.webkitTransform = 'translate3d(0, 0, 0)'
                    inner.style.transform = 'translate3d(0, 0, 0)'
                    el.addEventListener('transitionend', done) // Vue為了知道過渡的完成,必須設定相應的事件監聽器。它可以是transitionend或 animationend
                })
                // console.log(el);
            },
            afterEnter(el) {
                let ball = this.dropBalls.shift();
                if(ball) {
                    ball.show = false;
                    el.style.display = 'none';
                } 
                // console.log(el);
            }
    View Code

    每個鉤子都有一個引數el: 當前執行transition動畫的DOM物件

    當我們點選觸發一個過渡的時候,我們在beforeEnter裡先拿到當前元素的偏移位置,
    然後給過渡元素設定其起始位置,在enter裡需要重新觸發下瀏覽器的重繪,然後在下一幀重新設定元素的結束位置,
    這時就會產生過渡效果,在過渡完成後我們將當前元素隱藏即可。

    關於tansition實踐詳解部落格:【重點突破】—— Vue2.0 transition 動畫Demo實踐填坑

 

二、food商品詳情頁實現

 

food

 

       商品詳情頁實現-food.vue元件

  1. 設計時:父元件可以呼叫子元件方法,子元件不能呼叫父元件方法
  2. 常見命名習慣:如果是父元件呼叫的方法,命名如show();如果是元件私有方法,命名會在前面加_, 如_show()
  3. 詳情頁從右往左飛入動畫:
    .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30
        width: 100%
        background: #ffffff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
              transition: all 0.2s linear
              transform: translate3d(0, 0, 0)
        &.move-enter, &.move-leave-active
              opacity: 0
              transform: translate3d(100%, 0, 0)
  • 坑:頭圖顯示是非同步載入的,不能寫死寬高,因為視口是自適應的,但不設定高度,頁面內容會有圖片撐開高度的抖動過程。
  • 解決:一開始給<img>限制寬高,設為容器的100%
    .image-header
        position: relative
        width: 100%
        height: 0
        padding-top: 100% // 百分比是相對於盒子的寬度來計算的,看起來就像是一個正方形
        img 
           position: absolute
           top: 0
           left: 0
           width: 100%
           height: 100%
  • 坑:“加入購物車”一點選就會display:none,這樣執行better-scroll動畫的時候,找ball.el.getBoundingClientRect()會找不到,小球就不能找到正確的初始位置。
  • 解決:給消失的過程加一個opcity的transition動畫,時長0.2s,這樣就不會立刻消失
    &.buy-enter-active, &.buy-leave-active 
         transition: all 0.2s
         opacity: 1 
    &.buy-enter, &.buy-leave-active 
         opacity: 0
  • 坑:選單列表的“+”“-”按鈕,每次點選都會觸發詳情頁顯示,這是因為點選事件被穿透了。
  • 解決:給cart-control.vue元件中的“+”“-”按鈕的點選事件,都新增阻止事件冒泡
    @click.stop.prevent="decreaseCart($event)"
    @click.stop.prevent="addCart($event)"
  • 同理,詳情頁的“加入購物車”按鈕,最好也加上阻止事件冒泡
    @click.stop.prevent="addFirst($event)"

       split元件實現 

  • 一個很簡單的樣式模板元件,分隔區
    <template>
      <div class="split"></div>
    </template>
     
    <script type="text/ecmascript-6">
      export default {};
    </script>
     
    <style lang="stylus" rel="stylesheet/stylus">
        .split
            width 100%
            height 16px
            border-top: 1px solid rgba(1, 17, 27, 0.1);
            border-bottom: 1px solid rgba(1, 17, 27, 0.1);
            background: #f3f5f7
    </style>
    View Code

       商品評價 - ratingselect 元件

  1. 設定ratingselect元件中需要的props接收的資料,資料應從food.vue元件傳入<ratingselect></ratingselect>,並由ratingselect.vue的props接收
    <v-ratingselect :select-type="selectType" :only-content="onlyContent"
                    :desc="desc" :ratings="food.ratings" 
                    @increment="incrementTotal">
    </v-ratingselect>
  2. props的值如下:首先是有一個變數【only-content】是否顯示只看內容,還有一個【select-type控制選擇的型別還有要維護一個【ratings】所有評價的資料,因為這裡有一個評價數量;還要去維護一個【desc】描述,是(全部,推薦,吐槽)還是(全部,滿意,不滿意),按照以上標準設定外部元件傳入ratingselect的props值
    const POSITIVE = 0;
    const NEGATIVE = 1;
    const ALL = 2;
    
    export default {
            //需要一些評價資料才能完成評價元件
            props: {
               ratings: {
                   type: Array,
                   default() {
                       return [];
                   }
               },
                selectType: { //全部,滿意,不滿意
                    type: Number,
                    default: ALL //預設情況時ALL,值等於2
                },
                onlyContent: { //只看有內容的評價還是所有的評價
                    type: Boolean,
                    default: false //設定為可以看到所有的評價
                },
                desc: { //描述
                    type: Object,
                    default() { //預設desc是這三種,在商品詳情頁的時候傳入推薦或者吐槽
                        return {
                            all: '全部',
                            positive: '滿意',
                            negative: '不滿意'
                        };
                    }
                }
    },
  3. 在food.vue(商品詳情頁)中引入ratingSelect元件的時候,將desc改成"全部","推薦"和"吐槽",接下來寫DOM佈局:

    <template>
      <div class="ratingselect">
            <div class="rating-type" border-1px>
                <span>{{desc.all}}</span>
                <span>{{desc.positive}}</span>
                <span>{{desc.negative}}</span>
            </div>
            <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
                <span class="icon-check_circle"></span>
                <span class="text">只看有內容的評價</span>
            </div>
      </div>
    </template>
    View Code
  4. 在food.vue(商品詳情頁)的data中掛載對上述物件的跟蹤,並對其進行初始化

    const POSITIVE = 0;
    const NEGATIVE = 1;
    const ALL = 2;
    
    data () {
            return {
                showFlag: false,
                selectType: ALL,
                onlyContent: false, //先設定元件一開始顯示有內容的評價
                desc: { //desc做了改變
                    all: '全部',
                    positive: '推薦',
                    negative: '吐槽' 
                }
            };
    }
  5. 需求:在切換不同商品的時候能有相同的初始化狀態 —— 定義show()作為goods元件中呼叫food元件的函式,即點開商品詳情的顯示函式,將初始化設定傳入到show()中

    show() { //可以被父元件呼叫到,方法前加下劃線一般是私有方法
           this.showFlag = true;
           //初始化部分,ratingselect元件是被被不同的商品使用的,所以我們希望在點開不同的商品時,能有一樣的初始化狀態
           this.selectType = ALL;
           this.onlyContent = false;
           //展示介面時用到BScroll
           this.$nextTick(() => {
                    if (!this.scroll) {
                        this.scroll = new BScroll(this.$refs.food, {
                            click: true // 可以被點選
                        });
                    } else {
                       this.scroll.refresh();
                    }
           });
    }
  • 兩種樣式:公用樣式、特殊樣式
    .ratingselect
        .rating-type
            padding 18px 0
            margin 0 18px //保證橫線的長度
            border-1px(rgba(7,17,27,0.1))
            font-size 0
            .block //沒有寫文字的時候是沒有被撐開的
                display inline-block
                padding 8px 12px
                margin-right 8px
                border-radius 1px
                line-height 16px
                font-size 12px
                color rgb(77,85,93)
                &.active  // block的active要設定一下
                     color #ffffff
                .count
                    margin-left 2px
                    font-size 8px
                &.positive
                    background rgba(0,160,220,.2)
                    &.active
                        background rgb(0,160,220)
                &.negative
                    background  rgba(77,85,93,0.2)
                    &.active
                        background  rgb(77,85,93)
        .switch
            padding 12px 18px
            line-height 24px
            border-bottom 1px solid rgba(7,17,27,0.1)
            color rgb(147,153,159)
            font-size 0
            &.on
                .icon-check_circle
                    color #00c850
            .icon-check_circle
                display inline-block
                vertical-align top 
                margin-right 4px
                font-size 24px
            .text
                display inline-block
                vertical-align top
                font-size 12px       
    View Code
  1. 被選中 
    :class="{'active':selectType===2}"
  2. 居中對齊:
    display: inline-block
    vertical-align: top
  3. 因為rating下有一條border,所以在rating下不可以設定四周的padding值,如果設定了border就撐不開整個螢幕了

    .rating //因為要在rating title下方畫一條橫線,所以不能用padding-left,改用title的margin代替
         padding-top: 18px
         .title
             line-height 14px
             margin-left 18px
             font-size 14px
             color rgb(7,17,27)
  • 坑:[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “gems” (found in component: )
  • 這是因為在vue2.0中,直接修改prop是被視作反模式的。由於在新的渲染機制中,每當父元件重新渲染時,子元件都會被覆蓋,所以應該把props看做是不可變物件
  • 解決:在props中接收到父元件傳過來的selectType和onlyContent的值之後,在data中重新定義變數接收,以便觀測值的變化(因為子元件將改變data中的值,子元件要將這些變化的值傳遞個父元件)
    data() {
       return {
           sType : this.selectType,
           oContent : this.onlyContent
       }
    }
  • 之後,sType就替代了this.selectType,所以DOM就變成了
    <template>
      <div class="ratingselect">
            <div class="rating-type" border-1px>
                <span class="block positive" @click="select(2,$event)" :class="{'active':sType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span> </span>
                <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span>
                <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span>
            </div>
            <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
                <span class="icon-check_circle"></span>
                <span class="text">只看有內容的評價</span>
            </div>
      </div>
    </template>
    View Code
  • 編寫rating-type和swicth切換有內容評價部分的繫結函式:select(type, event) —— 在點選的時候就把型別123傳進去,傳入event是因為外層是一個betterScroll,要進行點選事件的判斷,將sType的值更新之後通過emit將函式派發出去;

    methods: {
          select (type, event) {        //點選的時候外層是有一個BScroll的,所以要傳遞event阻止預設點選
              if (!event._constructed) { //瀏覽器直接return掉,去掉自帶click事件的點選
                    return;
              }
              //將this.selectType設定成傳入的引數,而不是food傳過來的初始化的值,之後樣式就可以隨著點選改變了
              this.sType = type;         /派發事件通知父元件food.vue selectType的改變,將type值傳出去
    console.log('ratingselect.vue ' + type); this.$emit('increment', 'selectType', this.sType); }, toggleContent (event) { if (!event._constructed) { //瀏覽器直接return掉,去掉自帶click事件的點選 return; } this.oContent = !this.oContent; console.log('ratingselect.vue ' + this.oContent); this.$emit('increment', 'onlyContent', this
    .oContent); } }
  • 統計不同評價的數量(過濾評價型別),新增計算屬性 -- positives和negitives陣列,長度即為評價數量

    <div class="rating-type" border-1px>
       <span class="block positive" @click="select(2,$event)" :class="{'active':sType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span> </span>
       <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span>
       <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span>
     </div>
     computed: {
        positives() { //對應所有正向評價的陣列
                return this.ratings.filter((rating) => {
                      return rating.rateType === POSITIVE;
                });
        },
        negatives() {
                return this.ratings.filter((rating) => {
                      return rating.rateType === NEGATIVE;
                });
        }
    }

       評價列表

  • 切換子元件的按鈕之後,父元件就可以根據子元件的選擇進行內容的切換
  1. 為列表的顯示新增選擇
     <li v-show="needShow(rating.rateType, rating.text)" //v-show特殊用法:繫結函式返回值
    v-for
    ="rating in food.ratings"
    :key
    ="rating.id"
    class
    ="rating-item border-1px">
  2. 定義needshow() 
    needShow(type, text) {
         // console.log('this.selectType: ' + this.selectType + '  type: ' + type + ' out  ' + text);
         if (this.onlyContent && !text) {
                return false;
         }
         if (this.selectType === ALL) {
                return true;
         } else {
         //console.log('this.selectType: ' + this.selectType + 'type: ' + type + ' in ' + text);
                return type === this.selectType;
         }
    }
  3. ratingselect.vue  中進行rating.rateType的切換變數更改後的結果要傳遞到父元件中,這時用到了incrementTotal() 
    incrementTotal(type, data) {  // 對子元件更改的數值進行監聽
           this[type] = data;
           this.$nextTick(() => { // 當我們改變資料的時候,DOM的更新是非同步的
                    this.scroll.refresh();
           });
    }
  4. 觸發事件increment:  在子元件ratingselect中使用select和toggleContent中進行emit派發
  • 時間的顯示新增過濾器,將時間戳轉化為時間字串
    <div class="time">{{rating.rateTime | formatDate}}</div>
    import {formatDate} from 'common/js/date.js';
    export function formatDate(date, fmt) {
       if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
       }
      let o = {
        'M+': date.getMonth() + 1,
        'd+': date.getDate(),
        'h+': date.getHours(),
        'm+': date.getMinutes(),
        's+': date.getSeconds()
      };
      for (let k in o) {
        if (new RegExp(`(${k})`).test(fmt)) {
          let str = o[k] + '';
          fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
        }
      }
      return fmt;
    }
     
    function padLeftZero(str) {
      return ('00' + str).substr(str.length);
    }
    View Code
    filters: {
          formatDate(time) {
              let date = new Date(time);
              return formatDate(date, 'yyyy-MM-dd hh:mm');
          }  
    }

版權宣告:本文原創,未經本人允許不得轉載