餓了麼 vue 專案總結
專案完成之後 npm run build
這本來是寫在最後面一段的,我現在把他寫在了最前面,方便我們事先知道,整個專案做完之後是什麼樣子的
- 專案完成之後在 根目錄 下 npm run build (就是 npm run dev 的那個目錄)
- 會在根目錄下生成一個 dist 目錄,其中包含著 index.html 和一個css目錄,一個js目錄
- 按官方說,這個 dist 目錄必須 http server 環境下才能執行
- 下個 xampp 在本地服務下訪問
訪問時出現了以下幾個問題:
- css js 引用路徑出錯 (將 cofig目錄下的 index.js 裡的 assetsPublicPath:`./` 這樣設定即可)
- 由於視訊上是寫了一個 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>