前言
本節分為四大塊:
1. 商品信息(布局樣式、第三方插件庫better-scroll 的應用、split 組件)
2. 商品評價(ratingselect 組件)
3. 商品評價(評價列表)
PS:本節所有代碼在文章底部。
商品信息
1. CSS 設置

1 <style lang="stylus" rel="stylesheet/stylus"> 2 @import "../../common/stylus/mixin.styl" 3 4 .food 5 position fixed 6 left 0 7 top 0 8 bottom 48px 9 z-index 30 10 width 100% 11 background #fff 12 transform translate3d(0, 0, 0) 13 &.move-enter-active, &.move-leave-active 14 transition all 0.2s linear 15 &.move-enter, &.move-leave-active 16 transform translate3d(100%, 0, 0) 17 .image-header 18 position relative 19 width 100% 20 height 0 21 padding-top 100% 22 img 23 position absolute 24 left 0 25 top 0 26 width 100% 27 height 100% 28 .back 29 position absolute 30 left 0 31 top 10px 32 .icon-arrow_lift 33 display block 34 /*點擊區域變大*/ 35 padding 10px 36 font-size 20px 37 color #fff 38 .content 39 position relative 40 padding 18px 41 .title 42 margin-bottom 8px 43 line-height 14px 44 font-size 14px 45 font-weight 700 46 color rgb(7, 17, 27) 47 .detail 48 margin-bottom 18px 49 height 10px 50 line-height 10px 51 font-size 0 52 .sell-count, .rating 53 font-size 10px 54 color rgb(147, 153, 159) 55 .sell-count 56 margin-right 12px 57 .price 58 font-weight 700 59 line-height 24px 60 .now 61 margin-right 8px 62 font-size 14px 63 color rgb(240, 20, 20) 64 .old 65 text-decoration line-through 66 font-size 10px 67 color rgb(147, 153, 159) 68 .cartcontrol-wrapper 69 position absolute 70 right 12px 71 bottom 12px 72 .buy 73 position absolute 74 right 18px 75 bottom 18px 76 /*因為要蓋住cartcontrol組件*/ 77 z-index 10 78 height 24px 79 line-height 24px 80 padding 0 12px 81 box-sizing border-box 82 border-radius 12px 83 font-size 10px 84 color #fff 85 background-color rgb(0, 160, 220) 86 opacity 1 87 &.fade-enter-active, &.fade-leave-active 88 transition all 0.2s linear 89 &.fade-enter, &.fade-leave-active 90 opacity 0 91 z-index -1 92 .info 93 padding: 18px 94 .title 95 line-height: 14px 96 margin-bottom: 6px 97 font-size: 14px 98 color: rgb(7, 17, 27) 99 .text 100 line-height: 24px 101 padding: 0 8px 102 font-size: 12px 103 color: rgb(77, 85, 93) 104 .rating 105 padding-top: 18px 106 .title 107 line-height: 14px 108 margin-left: 18px 109 font-size: 14px 110 color: rgb(7, 17, 27) 111 .rating-wrapper 112 padding 0 18px 113 .rating-item 114 position relative 115 padding 16px 0 116 border-1px(rgba(7, 17, 27, 0.1)) 117 .user 118 position absolute 119 top 16px 120 right 0 121 line-height 12px 122 font-size 0 123 .username 124 margin-right 6px 125 display inline-block 126 vertical-align top 127 font-size 10px 128 color rgb(147, 153, 159) 129 .avatar 130 border-radius 50% 131 .time 132 margin-bottom 6px 133 line-height 12px 134 font-size 10px 135 color rgb(147, 153, 159) 136 .text 137 line-height 16px 138 font-size 12px 139 color rgb(7, 17, 27) 140 .icon-thumb_up, .icon-thumb_down 141 margin-right 4px 142 line-height 16px 143 font-size 12px 144 .icon-thumb_up 145 color rgb(0, 160, 220) 146 .icon-thumb_down 147 color rgb(147, 153, 159) 148 .no-rating 149 padding 16px 0 150 font-size 12px 151 color rgb(147, 153, 159) 152 </style>
1)相對於屏幕進行定位,使用 fixed 布局。
2)底部有購物車,所以設置 bottom 為 48px。
3)z-index 的值應該小於購物車詳情層的 z-index,因為購物車詳情層彈出應該要遮蓋住商品詳情層。
4)加上 transition 動畫
5)圖片高度應該和屏幕寬度一樣,是動態變化的。圖片加載是異步過程,如果不設置圖片高度,等到圖片加載完畢,頁面高度會突然被撐開。如何解決?先把寬高設好,設置 height 0,padding-top 100%。這是 W3C 的一個特定寫法,當 padding 的 top 或 bottom 設置為100% 時,會相對於盒子的 width 100%,這就相當於有一個寬高相等的盒子。(同理,當 padding 的 left 或 right 設置為100% 時,會相對於盒子的 height 100%)
2. 數據獲取

1 food.vue文件 2 props: { 3 food: { 4 type: Object 5 } 6 }, 7 8 9 goods.vue文件 10 <food :food="selectedFood" ref="food"></food> 11 12 import food from '../../components/food/food'; 13 data() { 14 return { 15 selectedFood: {} 16 }; 17 }, 18 methods: { 19 selectFood(food, event) { 20 if (!event._constructed) { 21 // eslint-disable-next-line 22 return; 23 } 24 this.selectedFood = food; 25 }, 26 }
1)商品詳情頁關聯的是商品本身,也就是食品分類下的每個商品。使用 props 接收 food 對象。
2)在 goods 組件中引用 food 組件,並且傳入food,設定變量為 selectedFood(即選中的 food),在 data 中定義該變量,為一個空對象。
3)selectedFood 是選中的 food,那么什么時候選中?當點擊 food 時。如何實現:在 li 中添加點擊事件 selectFood(food,$event),傳入參數 food,因為這里是 foodWrapper,所以點擊的時候也要拿到 event,接着在在 methods 中實現該方法。先 return 掉瀏覽器默認的點擊事件,將參數 food 傳遞給變量 selectedFood。如何檢驗:在瀏覽器調試窗口中找到 food 層,取消display屬性,看看布局有沒有錯。
3. show 方法的實現:點擊商品時,商品詳情層展開

1 food.vue文件 2 <div class="food" v-show="showFlag" ref="food"> 3 4 data() { 5 return { 6 showFlag: false 7 } 8 }; 9 }, 10 methods: { 11 show() { 12 this.showFlag = true; 13 }, 14 } 15 16 goods.vue文件 17 methods: { 18 selectFood(food, event) { 19 this.$refs.food.show(); 20 } 21 }
在 food 組件中定義 show 方法,show 方法通過改變 showFlag 的值來實現商品詳情層的展開功能,在此之前,需要在 data 中先定義 showFlag。然后在 goods 組件中通過 ref 獲取組件,並通過 $refs 調用子組件的 show 方法。
PS:1)父組件可以調用子組件方法,子組件不能調用父組件方法。2)設計規范:下划線開頭是私有方法(_drop)。
4. 給返回按鈕添加點擊事件 hide

1 <div class="back" @click="hide"> 2 3 methods: { 4 hide() { 5 this.showFlag = false; 6 } 7 }
1)CSS設置:(display block,padding 10px)先設置display,才能設置padding,使點擊區域變大。
2)hide 方法通過改變 showFlag 的值為 false 來實現返回功能。
設置其他樣式
5. better-scroll 的使用

1 <div class="food" v-show="showFlag" ref="food"> 2 <div class="food-content"> 3 </div> 4 </div> 5 6 methods: { 7 show() { 8 this.$nextTick(() => { 9 if (!this.scroll) { 10 this.scroll = new BScroll(this.$refs.food, { 11 click: true 12 }); 13 } else { 14 this.scroll.refresh(); 15 } 16 }); 17 } 18 }
原理:當內部(content)的內容高度大於視口高度,則產生滾動效果
固定框架:外層是一個 wrapper,里面有一個 content,並且這個 content 高度是由其中的內容撐開的。
使用:better-scroll 應該綁定在最外層(.food)。依舊是通過 ref 獲取組件,並通過 $refs 調用組件。better-scroll 應該在商品詳情層展開時就進行初始化,所以其初始化應該在 show 方法中。
6. 購物車模塊

1 food.vue文件: 2 <div class="cartcontrol-wrapper"> 3 <cartcontrol :food="food"></cartcontrol> 4 </div> 5 <transition name="fade"> 6 <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">加入購物車</div> 7 </transition> 8 9 import Vue from 'vue'; 10 methods: { 11 addFirst(event) { 12 console.log('click'); 13 if (!event._constructed) { 14 // eslint-disable-next-line 15 return; 16 } 17 this.$emit('add', event.target); 18 console.log(event.target); 19 Vue.set(this.food, 'count', 1); 20 } 21 } 22 23 cartcontrol.vue文件: 24 <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart"> 25 <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div>
有兩種樣式:當加入商品時,“加入購物車” 文字消失,顯示 cartcontrol 組件。所以應該加入兩個層,這兩個層平級,分別是 buy 層和 cartcontrol 層。
1)這兩個層都是絕對定位。
2)buy 層加入 transition 動畫及 v-show 指令(當 food.count 不存在或等於 0 時,該層顯示)。
3)buy 層進行樣式設計:z-index 10,因為要蓋住 cartcontrol 組件。
4)添加點擊按鈕事件 addFirst。首先阻止瀏覽器默認點擊事件,然后引入 vue,利用 Vue.set,將 this.count 置為1,最后使用 $emit
將事件事件傳出去。
PS:1)不傳參數的話,則默認參數為event。2)不傳參數得寫為addFirst,而不能寫成addFirst()。
Q1:在 buy 層點擊時,發現小球位下落置不正確。
A1:因為點擊后,count>0,此時 buy 層因為 v-show 指令,會被隱藏,display 瞬間變成 none。所以在 $nextTick 中做動畫時就找不到小球初始位置。
R1:將 buy 層的消失做成一個動畫(transition,漸隱效果),這樣 buy 層就不會立刻隱藏,display 屬性不會立刻被設置 none,位置就能被計算。
Q2:點擊商品頁(goods 組件)的小球,商品詳情頁會彈出。
A2:這是因為事件冒泡,所以要阻止事件冒泡。
R2:給 cartcontro l組件的加號和減號小球的點擊事件添加修飾符(@click.stop.prevent)。
6. 新建組件(components -> split ->split.vue),在 food.vue 引入注冊並使用。

1 <template> 2 <div class="split"></div> 3 </template> 4 5 <script type="text/ecmascript-6"> 6 export default {}; 7 </script> 8 9 <style lang="stylus" rel="stylesheet/stylus"> 10 .split 11 width: 100% 12 height: 16px 13 border-top: 1px solid rgba(7, 17, 27, 0.1) 14 border-bottom: 1px solid rgba(7, 17, 27, 0.1) 15 background: #f3f5f7 16 </style>
7. food.info 不是每個數據都有,所以 food.info 要加 v-show 指令。
商品評價(ratingselect 組件)
1. 新建組件(components -> ratingselect->ratingselect.vue)。
2. ratingselect 組件需要在 props 中先接收四個參數:一個變量控制評價數組(ratings),一個變量維護描述(desc),一個變量確定是否只看內容(onlyContent),一個變量維護選擇的評價類型(selectType)。

1 const POSITIVE = 0; // 正面評價 2 const NEGATIVE = 1; // 負面評價 3 const ALL = 2; // 所有評價 4 5 props: { 6 // 評價數組 7 ratings: { 8 type: Array, 9 default() { 10 return []; 11 } 12 }, 13 // 選擇評論類型 14 selectType: { 15 type: Number, 16 default: ALL 17 }, 18 // 只看有內容評論 19 onlyContent: { 20 type: Boolean, 21 default: false 22 }, 23 // 評論描述 24 desc: { 25 type: Object, 26 default() { 27 return { 28 all: '全部', 29 positive: '滿意', 30 negative: '不滿意' 31 }; 32 } 33 } 34 },
評價類型一般分為所有評價,正面評價和負面評價,將這三個定義為常量。評價類型默認選所有評價。
3. 在 food.vue 引入注冊並使用 ratingselect 組件,完成布局及CSS設置。

1 <div class="rating"> 2 <h1 class="title">商品評價</h1> 3 <ratingselect :selectType="selectType" :onlyContent="onlyContent" :desc="desc" :ratings="food.ratings" @select="selectRating" @toggle="toggleContent"></ratingselect> 4 </div> 5 6 .rating 7 padding-top: 18px 8 .title 9 line-height: 14px 10 margin-left: 18px 11 font-size: 14px 12 color: rgb(7, 17, 27) 13

1 <template> 2 <div class="ratingselect"> 3 <div class="rating-type border-1px"> 4 <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span 5 class="count">{{ratings.length}}</span></span> 6 <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span 7 class="count">{{positives.length}}</span></span> 8 <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span 9 class="count">{{negatives.length}}</span></span> 10 </div> 11 <div @click="toggleContent" class="switch" :class="{'on':onlyContent}"> 12 <span class="icon-check_circle"></span> 13 <span class="text">只看有內容的評價</span> 14 </div> 15 </div> 16 </template> 17 18 <style lang="stylus" rel="stylesheet/stylus"> 19 @import "../../common/stylus/mixin.styl" 20 21 .ratingselect 22 .rating-type 23 padding: 18px 0 24 margin: 0 18px 25 border-1px(rgba(7, 17, 27, 0.1)) 26 /* 消除空白字符影響 */ 27 font-size: 0 28 .block 29 display: inline-block 30 padding: 8px 12px 31 margin-right: 8px 32 line-height: 16px 33 border-radius: 1px 34 font-size: 12px 35 color: rgb(77, 85, 93) 36 &.active 37 color #fff 38 .count 39 margin-left: 2px 40 font-size: 8px 41 &.positive 42 background: rgba(0, 160, 220, 0.2) 43 &.active 44 background: rgb(0, 160, 220) 45 &.negative 46 background: rgba(77, 85, 93, 0.2) 47 &.active 48 background: rgb(77, 85, 93) 49 .switch 50 padding: 12px 18px 51 line-height: 24px 52 border-bottom: 1px solid rgba(7, 17, 27, 0.1) 53 color: rgb(147, 153, 159) 54 font-size: 0 55 &.on 56 .icon-check_circle 57 color: #00c850 58 .icon-check_circle 59 /* 設置對齊方式 */ 60 display: inline-block 61 vertical-align: top 62 margin-right: 4px 63 font-size: 24px 64 .text 65 display: inline-block 66 vertical-align: top 67 font-size: 12px 68 </style>
1)在 food.vue 文件中:.rating 為什么不設置全padding,而設置padding-top?因為下邊有一條邊框是貫穿整個界面的。
2)在 ratingselect.vue 文件中:.rating-type 為什么設置 margin 而不是padding?因為下面有一條線,寫 padding 會影響到線的位置。
4. 通過父組件傳入數據

1 <ratingselect :selectType="selectType" :onlyContent="onlyContent" :desc="desc" :ratings="food.ratings" 2 @select="selectRating" @toggle="toggleContent"></ratingselect> 3 4 const ALL = 2; 5 6 export default { 7 data() { 8 return { 9 showFlag: false, 10 selectType: ALL, 11 onlyContent: true, 12 desc: { 13 all: '全部', 14 positive: '推薦', 15 negative: '吐槽' 16 } 17 }; 18 }, 19 methods: { 20 show() { 21 this.selectType = ALL; 22 this.onlyContent = false; 23 } 24 }
首先在 data 中定義相關數據,然后在 show 中初始化這些數據。為什么要初始化?因為 ratingselect 組件被多個商品使用,當傳入不同商品時,希望這些屬性的狀態都是初始化的狀態。最后在父組件中傳入相關數據。注意:ratings 是 food.ratings。
5. 添加高亮設置

1 <template> 2 <div class="ratingselect"> 3 <div class="rating-type border-1px"> 4 <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span 5 class="count">{{ratings.length}}</span></span> 6 <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span 7 class="count">{{positives.length}}</span></span> 8 <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span 9 class="count">{{negatives.length}}</span></span> 10 </div> 11 <div @click="toggleContent" class="switch" :class="{'on':onlyContent}"> 12 <span class="icon-check_circle"></span> 13 <span class="text">只看有內容的評價</span> 14 </div> 15 </div> 16 </template> 17 18 19 .rating-type 20 .block 21 display: inline-block 22 padding: 8px 12px 23 margin-right: 8px 24 line-height: 16px 25 border-radius: 1px 26 font-size: 12px 27 color: rgb(77, 85, 93) 28 &.active 29 color #fff 30 .count 31 margin-left: 2px 32 font-size: 8px 33 &.positive 34 background: rgba(0, 160, 220, 0.2) 35 &.active 36 background: rgb(0, 160, 220) 37 &.negative 38 background: rgba(77, 85, 93, 0.2) 39 &.active 40 background: rgb(77, 85, 93) 41 .switch 42 padding: 12px 18px 43 line-height: 24px 44 border-bottom: 1px solid rgba(7, 17, 27, 0.1) 45 color: rgb(147, 153, 159) 46 font-size: 0 47 &.on 48 .icon-check_circle 49 color: #00c850
1)選擇類型
從圖中可以看到,每個區塊都會有一個高亮設置,可以給這些設置加上類名 active,通過判斷:比如“全部”按鈕,當 selectType===2 時(之前設置過常量:ALL = 2),應用高亮設置。
如何驗證:在 food 組件的 show 方法中,在屬性的初始化設置時,設 this.selectType = 1 或 0,然后到瀏覽器中查看效果。
2)“只看有內容的評價”區塊
動態綁定 on 樣式,只有當 onlyContent 值為 true 時應用樣式。
如何驗證?在 food 組件的 show 方法中,在屬性的初始化設置時,設 this.onlyContent = false,然后到瀏覽器中查看效果。
6. 添加點擊事件

1 <div class="rating-type border-1px"> 2 <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span 3 class="count">{{ratings.length}}</span></span> 4 <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span 5 class="count">{{positives.length}}</span></span> 6 <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span 7 class="count">{{negatives.length}}</span></span> 8 </div> 9 <div @click="toggleContent" class="switch" :class="{'on':onlyContent}"></div> 10 11 12 methods: { 13 select(type, event) { 14 if (!event._constructed) { 15 // eslint-disable-next-line 16 return; 17 } 18 this.$emit('select', type); 19 }, 20 toggleContent(event) { 21 if (!event._constructed) { 22 // eslint-disable-next-line 23 return; 24 } 25 this.$emit('toggle', event); 26 } 27 },

1 <ratingselect :selectType="selectType" :onlyContent="onlyContent" :desc="desc" :ratings="food.ratings" @select="selectRating" @toggle="toggleContent"></ratingselect> 2 3 methods: { 4 selectRating(type) { 5 this.selectType = type; 6 this.$nextTick(() => { 7 this.scroll.refresh(); 8 }); 9 }, 10 toggleContent() { 11 this.onlyContent = !this.onlyContent; 12 this.$nextTick(() => { 13 this.scroll.refresh(); 14 }); 15 } 16 }
1)給選擇類型按鈕增加 click 事件 select。
該方法需要傳入參數 selectType 和 event。為什么需要event?因為點擊該區塊,區塊外層有 better-scroll,實現點擊功能。這里同樣是以 $emit 方法將數據傳出去。父組件通過 @select 方法來改變 selectType 的值。
2)給 switch(“只看有內容的評價”區塊) 增加 click 事件 toggleContent。
這里同樣是以 $emit 方法將數據傳出去。父組件通過 @toggle 方法來改變 onlyContent 的值。
PS:
2. vue報錯:Module build failed: ParseError: unexpected "indent"
這里是因為 css 代碼中注釋應該是 /* 消除空白字符影響 */,而寫成 // 消除空白字符影響(這是自動補全功能自己添加的注釋符號,很奇怪),具體原因有待細究。
7. 評價數量

1 <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}} 2 <span class="count">{{ratings.length}}</span> 3 </span> 4 <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}} 5 <span class="count">{{positives.length}}</span> 6 </span> 7 <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}} 8 <span class="count">{{negatives.length}}</span> 9 </span> 10 11 computed: { 12 // 正面評價數組 13 positives() { 14 return this.ratings.filter((rating) => { 15 return rating.rateType === POSITIVE; 16 }); 17 }, 18 // 負面評價數組 19 negatives() { 20 return this.ratings.filter((rating) => { 21 return rating.rateType === NEGATIVE; 22 }); 23 } 24 }
全部評價:ratings.length
正面評價:定義正面評價數組 positives,其值應該可以通過 ratings 的值計算得出,使用 filter 方法對數組進行過濾,返回數組元素中 rateType 值為 0 的數組元素(就是正向評價)。
負面評價:與上同理。
商品評價(評價列表)
評價列表有兩種樣式:一種是像左邊:有評價,一種是像右邊:沒評價。
1. 布局及CSS設置
1)圖標字體:通過 :class 動態綁定相應的類名,這里因為是一個數組,所以可以同時定義兩個 key 及其對應的 value 值。
<span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span>
2)右側是絕對定位,定位到右上角。
PS:在瀏覽器中檢查樣式設置時,會發現即便不刷新網頁也可以正常顯示,這是因為 reload 方法會重新加載。但一般還是刷新一下比較好,因為如果過程中高度撐高(內層 content 的高度是靠里面的內容撐開的),better-scroll高度計算可能不正確,因為不會重新執行 refresh。
2. 點擊按鈕與列表綁定
通過點擊按鈕,過濾列表,判斷哪些顯示哪些不顯示。
1)在遍歷時添加 v-show 指令,該指令可以綁定一個函數 needShow(rating.rateType,rating.text),最終獲得函數計算后的返回值。
2)定義函數:如果選中了“只看有內容的評論(onlyContent)”,並且這條評論的內容(text)為空,則返回 false。否則執行接下來的邏輯:此時 onlyContent 為false 或者 text 不為空,這個情況下糾結着判斷selectType類型,如果 selectType === ALL,則返回 true,否則的話,判斷當前的 rateType 是不是等於選擇類型(rateType),是的話返回 true,不是的話返回 false。
3)給 selectRating 和 toggleContent 方法加上 refresh 方法,即點擊按鈕選擇后重新計算高度,防止列表回彈。因為改變數據時,DOM更新是異步的,所以將 refresh 方法寫在 $nextTick 接口中。
PS:v-show綁定對象、屬性、字段、函數。
3. 時間戳的轉換
需要的格式:2016-07-23 21:52
data.json中的格式:"rateTime": 1469281964000,
可以看到,在實際項目中需要現實的時間格式和數據給的時間格式是不一樣的,數據中給的是時間戳,必須轉換成時間字符串。
1)使用 vue 的 filters 方法,其語法就是在這之后加上 “ | ”,后面在跟上 filters 的名字 formatDate。
<div class="time">{{rating.rateTime | formatDate}}</div>
2)定義組件級別的 filters,filters 中有一個 formatDate(),formatDate() 接收一個參數,這個參數就是時間戳。
首先把時間戳轉換成 Date 類型的對象,存放在變量 date 中。然后寫一個名為 formatDate() 的函數,該函數是為了將 date 轉換成所需要的年月日時間的形式。給該函數傳兩個參數,一個是 date,另外一個是格式化的字符串 'yyyy-MM-dd hh:mm'。該方法是通用方法,可以在 common -> js 中單獨定義一個 js 文件去實現。
3)使用 js 模塊實現函數 formatDate(),在 js 中創建 date.js。
第一步:在 date.js 文件中 export 方法 formatDate(),然后在 food.vue 中調用該模塊。
第二步:實現 formatDate() 方法。
date輸入是時間類型,輸出是字符串。輸入除了string類型還有一個格式化的字符串(就是兩個參數),輸出就是把字符串轉成需要的模式。
可以利用正則表達式來寫。傳入的參數是yyyy-MM-dd hh:mm,可以利用正則表達式遍歷這個字符串,將符合規則的表達式替換需要的年月日形式。
除了年是4個,其余都是2個。這里就是將yyyy替換成為真實的數據。
先替換年:定義規則:匹配到以y開頭一個或多個的字符串。在字符串中查找匹配前面定義的規則(.test(fmt))。
舉個例子:這里傳入的fmt="yyyy-MM-dd hh:mm",在正則表達式中:+ 代表匹配前面的子表達式一次或多次。/(y+)/代表的規則是找到以y開頭的字符串。所以這里找到的是yyyy。
匹配到相應的字符串后,找到相應數據(比如:2016),然后替換掉符合規則的字符串,即最終替換結果為(2016-MM-dd hh:mm)。
如何實現:字符串有一個replace方法,第一個參數是被替換的字符串,第二個參數是替換的字符串。
RegExp.$1 代表的是找到的符合規則的字符串(即yyyy),getFullYear() 方法可返回一個表示年份的 4 位數字(假設這里是2016)。要通過substr將其轉換成 4 位的字符串。
substr(start,length) 方法可在字符串中抽取從 start 下標開始的指定數目的字符。length參數省略的話,則返回從開始位置到結尾的字串。
substr(4 - RegExp.$1.length):
RegExp.$1.length的值是4,則4 - RegExp.$1.length為0,表示從getFullYear()返回的數字中抽取第一個位置到最后一個位置的字串。也就是說,從返回的數字2016中抽取第一個位置到最后一個位置的字串,結果就是2016,不過類型由數字變成字符串,剛好符合replace方法對參數類型的限制。
再舉個例子:假設fmt="yy-MM-dd hh:mm",其余不變,則最后從2016抽取第三個位置到最后一個位置的字串,就是16
除了年份比較特殊,其余的都一樣,可以先定義一個對象o,這個對象是一個正則表達式,對應所替換的內容。
其中月份比較特殊,getMonth()方法返回的值是0到11,為了取到正確的值,要加1。
定義完表達式對象,接下來遍歷該對象。
要先通過RegExp方法去構造一個正則表達式,依舊使用反引號和花括號結合的方式傳入變量k,變量k的值是M+、d+這一類,構造完正則表達式,遍歷字符串fmt,看看有沒有符合規則的,有的話,替換真實數據。
PS:一個是定義死了正則表達式(/(y+)/),一個動態構造正則表達式(RegExp(`(${k})`))
先定義要替換的值,末尾的這一步:+ '',是為了將數字轉換成字符串。
替換這里需要注意,不能直接替換掉符合正則表達式規則的字符串,因為:比如月份中1到9月是單個數字,如果匹配到的字符串是M,就沒問題,如果匹配到的字符串是MM,要先在9前面加上一個0。所以要判斷匹配到的字符串的長度,如果是1,直接替換,否則補0。
定義一個補0的方法padLeftZero,如果str為9,補全之后為009,str長度為1,則取得字串是從抽取第二個位置到最后一個位置的字串,即09;
再舉一例:如果str為12,補全之后為0012,str長度為2,則取得字串是從抽取第二個位置到最后一個位置的字串,即12;
PS:
1. 用毫秒時間戳初始化日期對象
var timestamp=new Date().getTime(); console.log( new Date(timestamp) ); //Tue Jun 06 2017 11:06:59 GMT+0800 (中國標准時間)
var date3 = new Date( timestamp - 1 * 60 * 60 * 1000); console.log(date3); // Tue Jun 06 2017 10:06:59 GMT+0800 (中國標准時間)
說明:時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總秒數。時間戳唯一地標識某一刻的時間。
2. 這就是 js 模塊化編程思想。將一個通用的方法抽象成模塊,要使用的時候引入即可。
3. 兩種引入方式的區別:
import {formatDate} from '../../common/js/date';
import cartcontrol from '../../components/cartcontrol/cartcontrol';
cartcontrol 組件時是通過 export default 導出的,而模塊是通過 export function 導出的(不是默認)。也就是說這個模塊其實可以同時 export 多個 function,也可以 import 多個模塊:{formatDate,a}。
實現{formatDate}方法:
date輸入是時間類型,輸出是字符串。輸入除了string類型還有一個格式化的字符串(就是兩個參數),輸出就是把字符串轉成需要的模式。
可以利用正則表達式來寫。傳入的參數是yyyy-MM-dd hh:mm,可以利用正則表達式遍歷這個字符串,將符合規則的表達式替換需要的年月日形式。
除了年是4個,其余都是2個。這里就是將yyyy替換成為真實的數據。
先替換年:定義規則:匹配到以y開頭一個或多個的字符串。在字符串中查找匹配前面定義的規則(.test(fmt))。
舉個例子:這里傳入的fmt="yyyy-MM-dd hh:mm",在正則表達式中:+ 代表匹配前面的子表達式一次或多次。/(y+)/代表的規則是找到以y開頭的字符串。所以這里找到的是yyyy。
匹配到相應的字符串后,找到相應數據(比如:2016),然后替換掉符合規則的字符串,即最終替換結果為(2016-MM-dd hh:mm)。
如何實現:字符串有一個replace方法,第一個參數是被替換的字符串,第二個參數是替換的字符串。
RegExp.$1 代表的是找到的符合規則的字符串(即yyyy),getFullYear() 方法可返回一個表示年份的 4 位數字(假設這里是2016)。要通過substr將其轉換成 4 位的字符串。
substr(start,length) 方法可在字符串中抽取從 start 下標開始的指定數目的字符。length參數省略的話,則返回從開始位置到結尾的字串。
substr(4 - RegExp.$1.length):
RegExp.$1.length的值是4,則4 - RegExp.$1.length為0,表示從getFullYear()返回的數字中抽取第一個位置到最后一個位置的字串。也就是說,從返回的數字2016中抽取第一個位置到最后一個位置的字串,結果就是2016,不過類型由數字變成字符串,剛好符合replace方法對參數類型的限制。
再舉個例子:假設fmt="yy-MM-dd hh:mm",其余不變,則最后從2016抽取第三個位置到最后一個位置的字串,就是16
除了年份比較特殊,其余的都一樣,可以先定義一個對象o,這個對象是一個正則表達式,對應所替換的內容。
其中月份比較特殊,getMonth()方法返回的值是0到11,為了取到正確的值,要加1。
定義完表達式對象,接下來遍歷該對象。
要先通過RegExp方法去構造一個正則表達式,依舊使用反引號和花括號結合的方式傳入變量k,變量k的值是M+、d+這一類,構造完正則表達式,遍歷字符串fmt,看看有沒有符合規則的,有的話,替換真實數據。
PS:一個是定義死了正則表達式(/(y+)/),一個動態構造正則表達式(RegExp(`(${k})`))
先定義要替換的值,末尾的這一步:+ '',是為了將數字轉換成字符串。
替換這里需要注意,不能直接替換掉符合正則表達式規則的字符串,因為:比如月份中1到9月是單個數字,如果匹配到的字符串是M,就沒問題,如果匹配到的字符串是MM,要先在9前面加上一個0。所以要判斷匹配到的字符串的長度,如果是1,直接替換,否則補0。
定義一個補0的方法padLeftZero,如果str為9,補全之后為009,str長度為1,則取得字串是從抽取第二個位置到最后一個位置的字串,即09;
再舉一例:如果str為12,補全之后為0012,str長度為2,則取得字串是從抽取第二個位置到最后一個位置的字串,即12;
至此,food 組件已全部完成。
附代碼:

1 <template> 2 <transition name="move"> 3 <div class="food" v-show="showFlag" ref="food"> 4 <div class="food-content"> 5 <div class="image-header"> 6 <img :src="food.image"> 7 <div class="back" @click="hide"> 8 <i class="icon-arrow_lift"></i> 9 </div> 10 </div> 11 <div class="content"> 12 <h1 class="title">{{food.name}}</h1> 13 <div class="detail"> 14 <span class="sell-count">月售{{food.sellCount}}份</span> 15 <span class="rating">好評率{{food.rating}}%</span> 16 </div> 17 <div class="price"> 18 <span class="now">¥{{food.price}}</span><span v-show="food.oldPrice" class="old">¥{{food.oldPrice}}</span> 19 </div> 20 <div class="cartcontrol-wrapper"> 21 <cartcontrol :food="food"></cartcontrol> 22 </div> 23 <transition name="fade"> 24 <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">加入購物車</div> 25 </transition> 26 </div> 27 <split></split> 28 <div class="info" v-show="food.info"> 29 <h1 class="title">商品信息</h1> 30 <p class="text">{{food.info}}</p> 31 </div> 32 <split></split> 33 <div class="rating"> 34 <h1 class="title">商品評價</h1> 35 <ratingselect :selectType="selectType" :onlyContent="onlyContent" :desc="desc" :ratings="food.ratings" 36 @select="selectRating" @toggle="toggleContent"></ratingselect> 37 <!--評價列表--> 38 <div class="rating-wrapper"> 39 <!--有列表且列表不為空--> 40 <ul v-show="food.ratings && food.ratings.length"> 41 <li v-show="needShow(rating.rateType,rating.text)" v-for="rating in food.ratings" 42 class="rating-item border-1px"> 43 <div class="user"> 44 <span class="username">{{rating.username}}</span> 45 <img class="avatar" :src="rating.avatar" width="12" height="12"> 46 </div> 47 <div class="time">{{rating.rateTime | formatDate}}</div> 48 <p class="text"> 49 <span 50 :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span>{{rating.text}} 51 </p> 52 </li> 53 </ul> 54 <div class="no-rating" v-show="!food.ratings || !food.ratings.length">暫無評價</div> 55 </div> 56 </div> 57 </div> 58 </div> 59 </transition> 60 61 </template> 62 63 <script type="text/ecmascript-6"> 64 import Vue from 'vue'; 65 import BScroll from 'better-scroll'; 66 import {formatDate} from '../../common/js/date'; 67 import cartcontrol from '../../components/cartcontrol/cartcontrol'; 68 import split from '../../components/split/split'; 69 import ratingselect from '../../components/ratingselect/ratingselect'; 70 71 const ALL = 2; 72 73 export default { 74 props: { 75 food: { 76 type: Object 77 } 78 }, 79 data() { 80 return { 81 showFlag: false, 82 selectType: ALL, 83 onlyContent: true, 84 desc: { 85 all: '全部', 86 positive: '推薦', 87 negative: '吐槽' 88 } 89 }; 90 }, 91 methods: { 92 show() { 93 this.showFlag = true; 94 // 保持初始化 95 this.selectType = ALL; 96 this.onlyContent = false; 97 this.$nextTick(() => { 98 if (!this.scroll) { 99 this.scroll = new BScroll(this.$refs.food, { 100 click: true 101 }); 102 } else { 103 this.scroll.refresh(); 104 } 105 }); 106 }, 107 hide() { 108 this.showFlag = false; 109 }, 110 addFirst(event) { 111 console.log('click'); 112 if (!event._constructed) { 113 // eslint-disable-next-line 114 return; 115 } 116 this.$emit('add', event.target); 117 console.log(event.target); 118 Vue.set(this.food, 'count', 1); 119 }, 120 selectRating(type) { 121 this.selectType = type; 122 this.$nextTick(() => { 123 this.scroll.refresh(); 124 }); 125 }, 126 toggleContent() { 127 this.onlyContent = !this.onlyContent; 128 this.$nextTick(() => { 129 this.scroll.refresh(); 130 }); 131 }, 132 needShow(rateType, text) { 133 // 如果選中了“只顯示有內容的評論”,並且這條記錄的內容為空 134 if (this.onlyContent && !text) { 135 return false; 136 } 137 // 此時 onlyContent 為 false 或者有文本時 138 if (this.selectType === ALL) { 139 return true; 140 } else { 141 return rateType === this.selectType; 142 } 143 } 144 }, 145 filters: { 146 formatDate(time) { 147 let date = new Date(time); 148 return formatDate(date, 'yyyy-MM-dd hh:mm'); 149 } 150 }, 151 components: { 152 cartcontrol, 153 split, 154 ratingselect 155 } 156 }; 157 </script> 158 159 <style lang="stylus" rel="stylesheet/stylus"> 160 @import "../../common/stylus/mixin.styl" 161 162 .food 163 position fixed 164 left 0 165 top 0 166 bottom 48px 167 z-index 30 168 width 100% 169 background #fff 170 transform translate3d(0, 0, 0) 171 &.move-enter-active, &.move-leave-active 172 transition all 0.2s linear 173 &.move-enter, &.move-leave-active 174 transform translate3d(100%, 0, 0) 175 .image-header 176 position relative 177 width 100% 178 height 0 179 padding-top 100% 180 img 181 position absolute 182 left 0 183 top 0 184 width 100% 185 height 100% 186 .back 187 position absolute 188 left 0 189 top 10px 190 .icon-arrow_lift 191 display block 192 /*點擊區域變大*/ 193 padding 10px 194 font-size 20px 195 color #fff 196 .content 197 position relative 198 padding 18px 199 .title 200 margin-bottom 8px 201 line-height 14px 202 font-size 14px 203 font-weight 700 204 color rgb(7, 17, 27) 205 .detail 206 margin-bottom 18px 207 height 10px 208 line-height 10px 209 font-size 0 210 .sell-count, .rating 211 font-size 10px 212 color rgb(147, 153, 159) 213 .sell-count 214 margin-right 12px 215 .price 216 font-weight 700 217 line-height 24px 218 .now 219 margin-right 8px 220 font-size 14px 221 color rgb(240, 20, 20) 222 .old 223 text-decoration line-through 224 font-size 10px 225 color rgb(147, 153, 159) 226 .cartcontrol-wrapper 227 position absolute 228 right 12px 229 bottom 12px 230 .buy 231 position absolute 232 right 18px 233 bottom 18px 234 /*因為要蓋住cartcontrol組件*/ 235 z-index 10 236 height 24px 237 line-height 24px 238 padding 0 12px 239 box-sizing border-box 240 border-radius 12px 241 font-size 10px 242 color #fff 243 background-color rgb(0, 160, 220) 244 opacity 1 245 &.fade-enter-active, &.fade-leave-active 246 transition all 0.2s linear 247 &.fade-enter, &.fade-leave-active 248 opacity 0 249 z-index -1 250 .info 251 padding: 18px 252 .title 253 line-height: 14px 254 margin-bottom: 6px 255 font-size: 14px 256 color: rgb(7, 17, 27) 257 .text 258 line-height: 24px 259 padding: 0 8px 260 font-size: 12px 261 color: rgb(77, 85, 93) 262 .rating 263 padding-top: 18px 264 .title 265 line-height: 14px 266 margin-left: 18px 267 font-size: 14px 268 color: rgb(7, 17, 27) 269 .rating-wrapper 270 padding 0 18px 271 .rating-item 272 position relative 273 padding 16px 0 274 border-1px(rgba(7, 17, 27, 0.1)) 275 .user 276 position absolute 277 top 16px 278 right 0 279 line-height 12px 280 font-size 0 281 .username 282 margin-right 6px 283 display inline-block 284 vertical-align top 285 font-size 10px 286 color rgb(147, 153, 159) 287 .avatar 288 border-radius 50% 289 .time 290 margin-bottom 6px 291 line-height 12px 292 font-size 10px 293 color rgb(147, 153, 159) 294 .text 295 line-height 16px 296 font-size 12px 297 color rgb(7, 17, 27) 298 .icon-thumb_up, .icon-thumb_down 299 margin-right 4px 300 line-height 16px 301 font-size 12px 302 .icon-thumb_up 303 color rgb(0, 160, 220) 304 .icon-thumb_down 305 color rgb(147, 153, 159) 306 .no-rating 307 padding 16px 0 308 font-size 12px 309 color rgb(147, 153, 159) 310 </style>

1 <template> 2 <div class="split"></div> 3 </template> 4 5 <script type="text/ecmascript-6"> 6 export default {}; 7 </script> 8 9 <style lang="stylus" rel="stylesheet/stylus"> 10 .split 11 width: 100% 12 height: 16px 13 border-top: 1px solid rgba(7, 17, 27, 0.1) 14 border-bottom: 1px solid rgba(7, 17, 27, 0.1) 15 background: #f3f5f7 16 </style>

1 <template> 2 <div class="ratingselect"> 3 <div class="rating-type border-1px"> 4 <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span 5 class="count">{{ratings.length}}</span></span> 6 <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span 7 class="count">{{positives.length}}</span></span> 8 <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span 9 class="count">{{negatives.length}}</span></span> 10 </div> 11 <div @click="toggleContent" class="switch" :class="{'on':onlyContent}"> 12 <span class="icon-check_circle"></span> 13 <span class="text">只看有內容的評價</span> 14 </div> 15 </div> 16 </template> 17 18 <script type="text/ecmascript-6"> 19 const POSITIVE = 0; // 正面評價 20 const NEGATIVE = 1; // 負面評價 21 const ALL = 2; // 所有評價 22 23 export default { 24 props: { 25 ratings: { 26 type: Array, 27 default() { 28 return []; 29 } 30 }, 31 selectType: { 32 type: Number, 33 default: ALL 34 }, 35 // 只看有內容評論 36 onlyContent: { 37 type: Boolean, 38 default: false 39 }, 40 // 評論 41 desc: { 42 type: Object, 43 default() { 44 return { 45 all: '全部', 46 positive: '滿意', 47 negative: '不滿意' 48 }; 49 } 50 } 51 }, 52 methods: { 53 select(type, event) { 54 if (!event._constructed) { 55 // eslint-disable-next-line 56 return; 57 } 58 this.$emit('select', type); 59 }, 60 toggleContent(event) { 61 if (!event._constructed) { 62 // eslint-disable-next-line 63 return; 64 } 65 this.$emit('toggle', event); 66 } 67 }, 68 computed: { 69 // 正面評價數組 70 positives() { 71 return this.ratings.filter((rating) => { 72 return rating.rateType === POSITIVE; 73 }); 74 }, 75 // 負面評價數組 76 negatives() { 77 return this.ratings.filter((rating) => { 78 return rating.rateType === NEGATIVE; 79 }); 80 } 81 } 82 }; 83 </script> 84 85 <style lang="stylus" rel="stylesheet/stylus"> 86 @import "../../common/stylus/mixin.styl" 87 88 .ratingselect 89 .rating-type 90 padding: 18px 0 91 margin: 0 18px 92 border-1px(rgba(7, 17, 27, 0.1)) 93 /* 消除空白字符影響 */ 94 font-size: 0 95 .block 96 display: inline-block 97 padding: 8px 12px 98 margin-right: 8px 99 line-height: 16px 100 border-radius: 1px 101 font-size: 12px 102 color: rgb(77, 85, 93) 103 &.active 104 color #fff 105 .count 106 margin-left: 2px 107 font-size: 8px 108 &.positive 109 background: rgba(0, 160, 220, 0.2) 110 &.active 111 background: rgb(0, 160, 220) 112 &.negative 113 background: rgba(77, 85, 93, 0.2) 114 &.active 115 background: rgb(77, 85, 93) 116 .switch 117 padding: 12px 18px 118 line-height: 24px 119 border-bottom: 1px solid rgba(7, 17, 27, 0.1) 120 color: rgb(147, 153, 159) 121 font-size: 0 122 &.on 123 .icon-check_circle 124 color: #00c850 125 .icon-check_circle 126 /* 設置對齊方式 */ 127 display: inline-block 128 vertical-align: top 129 margin-right: 4px 130 font-size: 24px 131 .text 132 display: inline-block 133 vertical-align: top 134 font-size: 12px 135 </style>

1 <template> 2 <div> 3 <div class="goods"> 4 <div class="menu-wrapper" ref="menuWrapper"> 5 <ul> 6 <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" 7 @click="selectMenu(index,$event)"> 8 <span class="text border-1px"> 9 <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span>{{item.name}} 10 </span> 11 </li> 12 </ul> 13 </div> 14 <div class="foods-wrapper" ref="foodsWrapper"> 15 <ul> 16 <li v-for="(item,index) in goods" class="food-list food-list-hook"> 17 <h1 class="title">{{item.name}}</h1> 18 <ul> 19 <li @click="selectFood(food,$event)" v-for="food in item.foods" class="food-item border-1px"> 20 <div class="icon"> 21 <img :src="food.icon" width="57" height="57"> 22 </div> 23 <div class="content"> 24 <h2 class="name">{{food.name}}</h2> 25 <p class="desc">{{food.description}}</p> 26 <div class="extra"> 27 <span class="count">月售{{food.sellCount}}份</span><span>好評率{{food.rating}}%</span> 28 </div> 29 <div class="price"> 30 <span class="now">¥{{food.price}}</span><span v-show="food.oldPrice" 31 class="old">¥{{food.oldPrice}}</span> 32 </div> 33 <div class="cartcontrol-wrapper"> 34 <cartcontrol @add="addFood" :food="food"></cartcontrol> 35 </div> 36 </div> 37 </li> 38 </ul> 39 </li> 40 </ul> 41 </div> 42 <!--最低起送費(minPrice)和配送費(deliveryPrice)--> 43 <shopcart ref="shopcart" :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" 44 :min-price="seller.minPrice"></shopcart> 45 </div> 46 <!--商品詳情層--> 47 <food :food="selectedFood" ref="food"></food> 48 </div> 49 </template> 50 51 <script type="text/ecmascript-6"> 52 import BScroll from 'better-scroll'; 53 import shopcart from '../../components/shopcart/shopcart'; 54 import cartcontrol from '../../components/cartcontrol/cartcontrol'; 55 import food from '../../components/food/food'; 56 57 const ERR_OK = 0; 58 59 export default { 60 props: { 61 seller: { 62 type: Object 63 } 64 }, 65 data() { 66 return { 67 goods: [], 68 listHeight: [], // 存放大區間高度的數組 69 scrollY: 0, // 右側實時變化的y值 70 selectedFood: {} 71 }; 72 }, 73 created() { 74 this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; 75 this.$http.get('/api/goods').then((response) => { 76 response = response.body; 77 if (response.errno === ERR_OK) { 78 this.goods = response.data; 79 // console.log(this.goods); 80 this.$nextTick(() => { 81 this._initScroll(); 82 this.calculateHeight(); 83 }); 84 } 85 }); 86 }, 87 methods: { 88 selectFood(food, event) { 89 if (!event._constructed) { 90 // eslint-disable-next-line 91 return; 92 } 93 this.selectedFood = food; 94 this.$refs.food.show(); 95 }, 96 selectMenu(index, event) { 97 // console.log(index); 98 // console.log(event); 99 // 如果是瀏覽器原生點擊事件,返回,不執行接下來的代碼。(自定義派發的事件,constructed為true,而瀏覽器原生點擊事件是沒有這個屬性的) 100 if (!event._constructed) { 101 // eslint-disable-next-line 102 return; 103 } 104 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); 105 // 找到點擊對應的DOM元素 106 let el = foodList[index]; 107 this.foodsScroll.scrollToElement(el, 300); 108 }, 109 addFood(target) { 110 this._drop(target); 111 }, 112 // drop方法(父組件的私有方法)調用子組件(shopcart)的drop方法 113 _drop(target) { 114 // 體驗優化,異步執行下落動畫 115 this.$nextTick(() => { 116 this.$refs.shopcart.drop(target); 117 }); 118 }, 119 _initScroll() { 120 this.menuScroll = new BScroll(this.$refs.menuWrapper, { 121 // better-scroll 默認會阻止瀏覽器的原生 click 事件。當設置為 true,better-scroll 會派發一個 click 事件,我們會給派發的 event 參數加一個私有屬性 _constructed,值為 true。 122 click: true 123 }); 124 this.foodsScroll = new BScroll(this.$refs.foodsWrapper, { 125 // click: true的設置是因為cartcontrol組件中需要添加點擊事件 126 click: true, 127 probeType: 3 128 }); 129 // 當滾動時,實時監測滾動位置並返回 130 this.foodsScroll.on('scroll', (pos) => { 131 // pos.y是一個負值,abs:將負數轉為正數,round:取整 132 this.scrollY = Math.abs(Math.round(pos.y)); 133 }); 134 }, 135 calculateHeight() { 136 // 獲取每個大區間的li(標題和內容) 137 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); 138 let height = 0; 139 this.listHeight.push(height); 140 for (let i = 0; i < foodList.length; i++) { 141 let item = foodList[i]; 142 height += item.clientHeight; 143 this.listHeight.push(height); 144 } 145 } 146 }, 147 computed: { 148 // 左側索引 149 currentIndex() { 150 for (let i = 0; i < this.listHeight.length; i++) { 151 // 獲取區間上下范圍 152 let height1 = this.listHeight[i]; 153 let height2 = this.listHeight[i + 1]; 154 // 如果索引值在區間內或者是是最后一個值,返回當前索引值,如果沒有listHeight.length,返回0。 155 if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) { 156 return i; 157 } 158 } 159 return 0; 160 }, 161 // 返回被選中的商品(供shopcart組件使用) 162 selectFoods() { 163 let foods = []; 164 // 遍歷商品分類 165 this.goods.forEach((good) => { 166 // 遍歷分類下的商品 167 good.foods.forEach((food) => { 168 // 如果food.count存在,即大於0,證明商品被選中,將被選中商品放進數組里 169 if (food.count) { 170 foods.push(food); 171 } 172 }); 173 }); 174 return foods; 175 } 176 }, 177 components: { 178 shopcart, 179 cartcontrol, 180 food 181 } 182 }; 183 </script> 184 185 <style lang="stylus" rel="stylesheet/stylus"> 186 @import "../../common/stylus/mixin.styl" 187 .goods 188 display flex 189 position absolute 190 /*header:134px,tab:40px*/ 191 top 174px 192 bottom 46px 193 width 100% 194 overflow hidden 195 .menu-wrapper 196 /*三個參數:等分,占位情況,縮放空間*/ 197 flex 0 0 80px 198 width 80px 199 background #f3f5f7 200 .menu-item 201 display table 202 height 54px 203 width 56px 204 padding 0 12px 205 line-height 14px 206 &.current 207 position relative 208 /*要蓋住border,要在最頂層*/ 209 z-index 10 210 /*蓋住上邊的border*/ 211 margin-top -1px 212 background #fff 213 .text 214 border-none() 215 font-weight 700 216 .icon 217 display inline-block 218 vertical-align top 219 width 12px 220 height 12px 221 margin-right 2px 222 background-size 12px 12px 223 background-repeat no-repeat 224 &.decrease 225 bg-image('decrease_3') 226 &.discount 227 bg-image('discount_3') 228 &.guarantee 229 bg-image('guarantee_3') 230 &.invoice 231 bg-image('invoice_3') 232 &.special 233 bg-image('special_3') 234 .text 235 display table-cell 236 width 56px 237 vertical-align middle 238 border-1px (rgba(7, 17, 27, 0.1)) 239 font-size 12px 240 .foods-wrapper 241 flex 1 242 .title 243 padding-left 14px 244 height 26px 245 line-height 26px 246 border-left 1px solid #d9dde1 247 font-size 12px 248 color rgb(147, 153, 159) 249 background #f3f5f7 250 .food-item 251 display flex 252 margin 18px 253 padding-bottom 18px 254 border-1px(rgba(7, 17, 27, 0.1)) 255 &:last-child 256 border-none() 257 margin-bottom 0 258 .icon 259 flex 0 0 57px 260 margin-right 10px 261 .content 262 flex 1 263 .name 264 margin 2px 0 8px 265 height 14px 266 line-height 14px 267 font-size 14px 268 color rgb(7, 17, 27) 269 .desc, .extra 270 font-size 10px 271 color rgb(147, 153, 159) 272 .desc 273 margin-bottom 8px 274 line-height 12px 275 .extra 276 line-height 10px 277 .count 278 margin-right 12px 279 .price 280 font-weight 700 281 line-height 24px 282 .now 283 margin-right 8px 284 font-size 14px 285 color rgb(240, 20, 20) 286 .old 287 text-decoration line-through 288 font-size 10px 289 color rgb(147, 153, 159) 290 .cartcontrol-wrapper 291 position absolute 292 right 0 293 bottom 12px 294 </style>

1 <template> 2 <div class="cartcontrol"> 3 <transition name="move"> 4 <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart"> 5 <span class="inner icon-remove_circle_outline"></span> 6 </div> 7 </transition> 8 <div class="cart-count" v-show="food.count>0">{{food.count}}</div> 9 <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div> 10 </div> 11 </template>