項目效果預覽 ele效果預覽
項目源碼地址 ele源碼
跟着慕課網黃軼老師 敲餓了麼 vue 項目
做者項目源代碼地址css
這原本是寫在最後面一段的,我如今把他寫在了最前面,方便咱們事先知道,整個項目作完以後是什麼樣子的html
訪問時出現瞭如下幾個問題:vue
添加靜態資源文件,修改 build、dev-serve.js mock模擬數據,
添加 meta 標籤
碰到 換臺機器 報錯-沒有 modules ,暫時解決方法,刪除整個 node_modules,而後從新 npm installnode
學習了 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. 增長小球動畫 實現過程: 一、小球最終的落點都是一致的,在左下角購物車按鈕處 (transform:translate(0,0,0)) 二、傳遞點擊的 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>