1. 程式人生 > >vue-餓了麽項目總結

vue-餓了麽項目總結

splay 如果 selector moc 狀態 最終 計算屬性 regexp 一段

這本來是寫在最後面一段的,我現在把他寫在了最前面,方便我們事先知道,整個項目做完之後是什麽樣子的

  • 項目完成之後在 根目錄 下 npm run build (就是 npm run dev 的那個目錄)
  • 會在根目錄下生成一個 dist 目錄,其中包含著 index.html 和一個css目錄,一個js目錄
  • 按官方說,這個 dist 目錄必須 http server 環境下才能運行
  • 下個 xampp 在本地服務下訪問
    訪問時出現了以下幾個問題:
  1. css js 引用路徑出錯 (將 cofig目錄下的 index.js 裏的 assetsPublicPath:‘./‘ 這樣設置即可)
  2. 由於視頻上是寫了一個 node 後端服務,訪問本地的 data.json 文件,然後用 vue-resource 訪問這個 node 服務才請求到的接口
    打包之後,訪問不到這個 node 服務了,自然就出錯了
    如何解決:我在朋友的幫助下,知道了 easy-mock這個東西,然後 把data.json 文件 用 easy-mock 制作成了 一個 http 接口
    後來因為 github 不能訪問 http 接口,又把 http 改成了 https(我最後打包的項目放在了 gitpages上了)

項目啟動

添加靜態資源文件,修改 build、dev-serve.js mock模擬數據,
添加 meta 標簽
碰到 換臺機器 報錯-沒有 modules ,暫時解決方法,刪除整個 node_modules,然後重新 npm install

建立好 es6 書寫, stylus書寫方法,增加了tab導航欄,配置好了路由

學習了 1px 邊框制作(不過感覺用處不大)
編寫 stylus mixin 函數並在引用
(註意:引入外界stylus樣式文件時:只能用 @import 在style標簽裏引用
且路徑不可以在 webpack.base.conf.js alias別名)

全局通用樣式,字體文件,圖標文件
可以用統一在同級目錄下用一個 index.styl
文件作為出口,在其內部 用 @import ‘./minix.styl‘ 引入
然後在再 webpack.base.conf.js  統一配置 alias 別名
之後再在 main.js  引入這個 index.styl 文件 即可使用這些樣式文件
如:import ‘common/stylus/index.styl‘

stylus 文件書寫
    1.盡量使用類 css 語法即 {}
    2.盡量避免拷貝代碼,產生多余的空格縮進問題
做完之後好好學習一下 flex 布局
display:flex  flex:1
完成 header 組件 ,goods組件 完成布局

better-scroll 的用法

better-scroll 實現列表滾動聯動
1. 初始化 better-scroll
    _initScroll() {
        this.menuScroll = new BScroll(this.$refs.menuWrapper,{
            click:true        //默認派發點擊事件
        });

        this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
            click:true,
            probeType:3   //實時偵測滾動
        });
    },

2. 在 vue 鉤子函數 created 內 this.$nextTick 回調裏面調用 better-scroll初始化函數

菜單欄根據foodList列表滾動實時高亮

1. 通過 _calculateHeight 方法動態計算出 每個列表的標題 的 clientHeight 值,並將其推進一個 listHeight 數組
2. 當滾動 foods 列表時,會動態計算出 pos.y 的值,
3. 把這個 pos.y 的值在計算屬性裏判斷 其在 listHeight 數組中對應的 index 值
4. 然後將菜單列表數組中的 index 值 設置為高亮 

點擊左側菜單欄,右側 foods 列表實時滾動到相應位置

1. 給 menu-item 綁定一個 setMenu(index) 方法
2. 然後根據這個 index 獲取foodslist 裏面對應的 li dom 元素
3. 利用 scrollToElement(el,100) api 自動將foodlist滾動到合適位置

selectMenu(index) {
    // 因為有自動派發事件,所以需要阻止,
    if(!event._constructed) return; 
    console.log(index);
    let foodList = this.$refs.foodList;  //通過 $refs.foodList獲取當前dom元素
    let el = foodList[index];
    this.foodsScroll.scrollToElement(el,10);
}  

購物車計算屬性使用

1. 將 item.foods 數據 通過 props 屬性傳遞到子組件(cartcontrol組件)

2. 在 cartcontrol 組件內 執行  addCart、decreaseCart 方法改變  item.foods.count 的值

    如果 item.count 值不存在,使用  Vue.set(this.food,‘count‘,1) ; 
    給foods增加 count 屬性,如果直接增加 count 屬性,不會產生響應式數據,必須用  Vue.set() 方法

3. 在子組件改變 item.foods對象的值,相應的父組件內的 item的值會隨之改變(js復雜數據類型地址引用)

4. 在父組件 goods.vue 利用計算屬性 動態的生成購物車數據,然後通過 props屬性傳遞給 shopcart.vue 組件

    計算屬性的計算出的值為響應式數據可以直接拿來使用,即在  v-for 中直接遍歷  selectFoods 
    
    // 選中的商品即購物車內的商品
    selectFoods() {
        let foods = [];
        this.goods.forEach((good) => {
            good.foods.forEach((food) => {
                if(food.count){
                    foods.push(food);
                }
            })
        });
        console.log(foods);
        return foods;
    }
    
    

cartcontrol 增加和減少商品小球動畫


1. 減少商品小球動畫
    利用 vue transition 組件-過度動畫 和 v-show 配合 可以給任何元素和組件添加 entering/leaving過度 
    條件渲染 (使用 v-if)
    條件展示 (使用v-show)
    動態組件
    組件根節點
    當插入或刪除包含在 transition 組件中的元素時,Vue將做如下處理:
    1.自動嗅探目標元素是否應用了 css 過度或動畫,如果是在恰當的時機添加/刪除 css 類名
    2.如果過渡組件提供了 JavaScript鉤子函數,這些鉤子函數將在恰當的時機被調用
    3.如果沒有找到鉤子並且也沒有檢測到css動畫,DOM操作(插入/刪除)在下一幀中立即執行

    過度的 css 類名
    1. v-enter 定義進入過渡的開始狀態,在元素插入式時生效,在下一幀移除
    2. v-enter-active 定義進入過渡的結束狀態。在元素被插入時生效,在 transition/animation 完成之後移除
    3. v-leave 定義離開過度的開始狀態。在離開過渡被觸發時生效,在下一幀移除
    4. v-leave-active 定義離開過渡的結束狀態,在離開過渡被觸發時生效,在下一幀被移除
    html:
    <transition name="move">
        <!-- 父元素用於控制小球 透明度變化 -->
        <div class="decrease" v-show="food.count>0">
            <!-- 子元素用於控制小球旋轉變化 -->
            <span class="inner icon-remove_circle_outline"></span>   
        </div>
    </transition>
    css:
    <!-- 小球enter之後最終結束時的狀態 -->
    .decrease{
        transition:all 0.4s linear;
        transform:translate3d(0,0,0);
        opacity:1;
        .inner{
            transition:all 0.4s linear;
            transform:rotate(0deg);
        }
    }
    <!-- 小球剛剛enter的狀態和小球leave-active狀態 -->
    &.move-enter,&.move-leave-active{
        transition:all 0.4s linear;
        transform:translate3d(24px,0,0);
        opacity:0;
        .inner{
            transform:rotate(180deg);
        }
    }
2. 增加小球動畫

    實現過程:

    1、小球最終的落點都是一致的,在左下角購物車按鈕處 (transform:translate(0,0,0))

    2、傳遞點擊的 dom 對象
        在 cartcontrol 組件裏點擊 + 時, 將點擊的 dom 元素,通過通過 $emit 派發給父組件 goods.vue
        this.$emit(‘add‘,event.target);
        <div class="cart-wrapper">
            <!-- add自定義事件用於派發當前點擊的dom元素,add為子組件方法,addFood為父組件方法 -->
            <cartcontrol :food="food" @add="addFood"></cartcontrol>
        </div>
        // 子組件$emit派發而來的事件
        addFood(target) {
            this._drop(target);  //傳遞 target
        },
        _drop(target) {
            // 體驗優化,異步執行下落動畫
            this.$nextTick(() => {
            //調用 shopcar 組件中的 drop 方法,向 shopcar組件 傳入當前點擊的 dom 對象
                this.$refs.shopcart.drop(target);
            });
        }

    3.在 shopcar 組件裏,創建 小球 dom 結構

        <!-- 小球容器 -->
        <div class="ball-container">
            <div v-for="ball in balls">
                <!-- 過度鉤子函數 -->
                <transition name="drop" v-on:before-enter="beforeDrop" v-on:enter="dropping" v-on:after-enter="afterDrop">
                    <!--  外層縱向運動,內層橫向運動-->
                    <div class="ball" v-show="ball.show">
                        <div class="inner inner-hook"></div>
                    </div>
                </transition>
            </div>
        </div>


    4. 創建 一個小球數組,內置5個對象(5個小球,均有 show 屬性,初始值為false)
        以便在多次快速點擊時,屏幕出現多個小球
        5個小球的初始位置 均在 左下角 購物車按鈕處
        創建一個 dropBalls 數組用於存儲 處在下落過程中的小球
        執行下落時 將 父組件傳遞過來的 dom 對象 當做一個屬性 給 ball,方便 在下面的方法中計算 ball 的位置
        data() {
            return {
                // 創建5個小球用於動畫
                balls:[{show:false},{show:false},{show:false},{show:false},{show:false}],
                dropBalls:[], // 存儲下落小球
            }
        },
    5.執行 v-on:before-enter="beforeDrop"  過度前鉤子函數
        設置 ball 初始位置,計算處 初始位置與目標位置的 差值 x,y ,將小球 transform :translate(x,y,0)到動畫初始位置

    6.執行 v-on:enter="dropping"  過度中鉤子函數
        手動觸發瀏覽器重繪,將 ball 通過 transform :translate(0,0,0) 移動到目標位置

    7. 執行 v-on:after-enter="afterDrop"  過度結束鉤子函數
        從存儲下落小球的數組裏 unshift 當前小球
        並將當前小球 display:none; show:false

    8.樣式
    .ball-container{
        //外層 做縱向運動
        .ball{
            position:fixed
            left:32px
            bottom:22px
            z-index:200
            //y 軸 貝塞爾曲線
            transition:all 2s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            //內從做橫向運動
            .inner{
                width:16px
                height:16px
                border-radius:50%
                background-color:rgb(0,160,220)
                //x 軸只需要線性緩動
                transition:all 2s linear
            }
        }

購物車列表的顯示隱藏狀態

    按鈕控制 fold => fold 控制 => listShow , listShow => 控制狀態顯示 (在totalCount>0)
    在 data 選項裏,定義一個 fold(折疊,true) 控制購物車的顯示隱藏狀態
    在 computed 計算屬性裏,定義一個 listshow 方法,來表示購物車列表的顯示隱藏狀態

    listShow() {
        if(!this.totalCount){  //假如所選商品為 0 ,return 掉結果,並將 fold 置為初始值
            this.fold = true;
            return false;
        }
        let show = !this.fold; // 否則,取 fold 的反值,靠 fold 的變化來 決定 列表顯示與否
        return show;
    }

    在 method 方法裏有個 toggleList 方法控制 fold 狀態
    toggleList(){
        if(!this.totalCount){
            return;
        }
        this.fold = !this.fold;
    },

詳情頁組件

    將選中的商品 通過 props 傳給 子組件
    <food @add="addFood" :food="seeFoodinfo" ref="food"></food>
    food 組件 通過 $emit 將food 組件添加購物車按鈕傳遞給 父組件 以便實現小球動畫

    addFood(target){
        console.log(target);
        //當前組件必須在父組件 引入處,bangding @add="xxx",繼而執行 父組件的 xxx 方法
        this.$emit(‘add‘,target);
    },

    詳情頁 過渡動畫
    <transition name="fade" ></transition>

    &.fly-enter-active, &.fly-leave-active {
        transition: all 0.2s linear
    }
    &.fly-enter, &.fly-leave-active {
        transform: translate3d(100%, 0, 0)
    }

ratingselect 組件(評價選擇組件)

    1. 評價組件
    全部、推薦、吐槽 類似一個 tab 選項卡的欄目
    只看有內容的評價 篩選
    因為整個項目會有兩個地方有這個東西,所以將其抽象為 ratingselect 組件

    組件書寫:
    上邊是一個 tab 選項卡
    1. 定義 三個常量 代表這三種狀態
        const Positive = 0;     //推薦
        const Negative = 1;     //吐槽
        const All = 2;          //全部

        <div class="rating-type border-1px">
            <span @click="select(2,$event)" class="block positive" :class="{‘active‘:selectType===2}">{{desc.all}} <span class="count">{{ratings.length}}</span></span>
        </div>
        在點擊事件中,將這三個狀態,發送給 父組件
        由於這 三個選項 的 選中狀態,是由父組件(food.vue)父組件通過 props 傳遞過來的,所以不可以在子組件中修改

        select(type,event){
            if(!event._constructed){
                return;
            }
            //不可以在子組件內,隨意改變父組件傳過來的值,通過 $emit 將子組件需要改變的值,發送給父組件,然後父組件在通過 props 傳給 子組件,然後 view 就會發生相應的改變
            this.$emit(‘select‘,type);
        }


        父組件:
            使用子組件
            <ratingselect
                @select="selectRating"
                @onlyContent="toggleContent"
                :ratings="food.ratings"
                :selectType="selectType"
                :onlyContent="onlyContent"
                :desc="desc"
            ></ratingselect>
            
            //在 父組件 methods 對象中 用 selectRating 方法接收子組件 emit 過來的值,賦值給 父組件 selectType 然後在通過 props傳遞給子組件,從而實現改變
            selectRating(type){
                this.selectType = type;
                this.$nextTick(()=> {
                    this.scroll.refresh();
                })
            },

            //只看有內容的 評價 也是同理 

food.vue 組件中的時間轉換函數

    在 common 目錄下創建一個公共工具函數 utils.js ,然後在需要用到的 組件中,進行 import 引入 

    utils.js
        export 
            function formatDate(fmt){
                ......
            }


    在 food 組件中使用,只需用 import 引入要使用到的 方法 即可
    import { format } from ‘common/js/utils‘
    在組件中即可直接使用 該方法

 food.vue 裏這種列表布局

    上下左右的間距,用 padding 撐開
    左邊 用 flex 給個固定的尺寸 flex: 0 0 28px
    右側 用 flex:1 ,右側剩余空間 自動充滿
    然後右側內容自然流布局,上下 margin 分配
    右側時間采用絕對定位
    布局:清晰簡單明了
    
    一般情況下:列表中文字垂直居中的布局一般用 上下 padding 撐開,不要直接設置高度,用line-height居中
    文字高度用 line-height 撐開

商家頁面(seller.vue) 商家實景頁面

    商家實景左右滾動列表圖片
    
    先根據圖片尺寸和左右 margin 計算出 list 列表容器的 寬度,然後 用 better-scroll 進行左右滾動

    一般情況下,要在 vue mounted 之後就可以初始化 better-scroll 
    但是這時候,圖片資源還沒有請求到,所以無法得知 圖片的 pics 的 length,繼而無法得知,列表容器的寬度

    解決辦法:
    vue 提供了一個 watch 對象,來用來監測數據的變化
    當 watch 監測到 seller 數據的變化,然後調用 _initPicScroll,初始化 better-scroll 
    watch:{
        ‘seller‘(){
            this.$nextTick(()=>{
                this._initPicScroll();
            })
        }
    },
    methods:{
        _initPicScroll() {
            if(this.seller.pics){
                let picWidth = 120;
                let margin = 6;
                let width = this.seller.pics.length * (picWidth + margin) - margin;
                this.$refs.picList.style.width = width + ‘px‘;
                
                //better-scroll左右滾動
                this.picScroll = new BScroll(this.$refs.picWrapper,{
                    scrollX: true,
                    eventPassthrough: ‘vertical‘
                })
            }
        }
    }

  利用localStorage 在本地收藏商家

    收藏商家是放在本地緩存 localStorage 裏的

    #1. 在 common/js/utils 文件裏創建兩個公共函數函數 寫入 localStorae 和 讀取 localStorage 
    # 2.  在點擊收藏按鈕時,調用存儲 方法,首次進入頁面時,調用 讀取方法

    由於 確定收藏與否的 favorite 屬性,是在 data 選項上被vue監測的,所以在data 選項上 favorite 是一個立即執行函數

    data:{
        favorite: ( () => {
            // 要讀取的對象,key值,默認值
            return loadFromLocal(this.seller.id, ‘favorite‘, false);    
        } )()
    }  

路由切換時,各組件會保持原來的狀態

    # 在路由外連上加上  <keep-alive> 即可
    <!-- 路由外鏈 -->
        <keep-alive>
            <router-view :seller="seller"></router-view>
        </keep-alive>

vue-餓了麽項目總結