商品詳情頁(food組件)


前言

本節分為四大塊:

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>
CSS 設置

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     }
show方法

在 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 }
hide方法

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 }
better-scroll 的使用

原理:當內部(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>
split.vue

 

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     },
props接收的變量

評價類型一般分為所有評價,正面評價和負面評價,將這三個定義為常量。評價類型默認選所有評價。

 

 

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      
food.vue
 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>
ratingselect.vue

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     },
ratingselect.vue
 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     }
food.vue

1)給選擇類型按鈕增加 click 事件 select。

該方法需要傳入參數 selectType 和 event。為什么需要event?因為點擊該區塊,區塊外層有 better-scroll,實現點擊功能。這里同樣是以 $emit 方法將數據傳出去。父組件通過 @select 方法來改變 selectType 的值。

2)給 switch(“只看有內容的評價”區塊) 增加 click 事件 toggleContent。

這里同樣是以 $emit 方法將數據傳出去。父組件通過 @toggle 方法來改變 onlyContent 的值。

PS:

1. props雙向綁定變成單向

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>
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>
split.vue
  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>
ratingselect.vue
  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>
goods.vue
 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>
cartcontrol.vue

 

打賞

免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2021 CODEPRJ.COM