在學習了 vue 之後,決定做一個小練習,仿寫了一個有關購物商城的小專案。下面就對專案做一個簡單的介紹。
專案原始碼: github
專案的目錄結構
-assets 與專案有關的靜態資源,包括 css,以及一些 images
-common 公共的工具類方法
-components 公共元件
-common 與專案耦合度較低的元件
-content 與專案耦合度較高的元件
-network 網路請求相關
-router 路由相關
-store vuex 相關
-views 主要展示的頁面
-home 首頁
-childcomps
-detail 詳情頁
-childcomps
-cart 購物車頁面
-childcomps
-profile 個人資訊頁面
-childcomps
劃分好目錄結構後就可以對每個模組進行獨立開發
主要實現的功能
首頁
- 頂部輪播圖的展示
- 中間選項卡點選可進行商品的切換以及吸頂效果
- 點選商品進入商品詳情頁
- 上拉展示更多商品
- 點選按鈕回到頂部
詳情頁
- 根據首頁中使用者的點選進行每個商品的獨立展示
- 點選商品資訊對應的標題跳轉到對應的內容區域
- 滑動頁面過程中與頂部商品資訊的標題聯動效果
- 點選按鈕回到頂部
- 加入購物車跳出彈窗
購物車頁面
- 展示各種商品的數量
- 底部工具欄展示選中商品的總價格
- 全選與取消全選
- 結算時的兩種彈窗
我的頁面
- 個人資訊的展示
- 點選我的購物車進入到購物車頁面
主要思路介紹
底部 tabbar 的封裝
移動端中比較常見的一種導航欄,需要根據使用者的點選進行跳轉到相應的頁面,所以直接封裝一個公共的元件。佈局一般是圖片加文字,圖片一般有兩種,需要根據當前是否處於活躍狀態的路由和使用者的點選來判斷展示哪張。內部預留插槽,使用者根據實際需求選擇底部導航的個數。每個導航為其配置路由。
頂部導航 navbar 的封裝
頂部的導航基本會出現在每個頁面中,所以也直接封裝為一個公共元件,佈局通常是左中右三欄佈局,所以元件內部預留三個插槽,採用 flex 佈局。
資料請求
對 axios 再封裝為一個 request.js 檔案,讓所有的頁面都基於這個檔案傳送請求,這樣做可以避免重複操作,提高了專案後期的可維護性。
在這個專案中將每個頁面需要傳送請求的部分又進行了一層封裝,即每個頁面都有獨立的與之相對應的傳送請求的檔案,首頁傳送請求只需要面向 home.js,詳情頁傳送請求面向 detail.js。
滾動元件 Scroll
- 引入 better-scroll 來替換掉頁面的原生滾動,Scroll.vue 是對 better-scroll 的封裝,為了提高元件的可複用性,可以讓 probeType, pullUpLoad,等值從外部傳入(專案中 click 的值為 true)。
- Scroll.vue 內程式碼的封裝:
建立一個 Better-Scroll 物件,傳入 DOM 和 引數( probeType,click,pullUpLoad等),
監聽 scroll 事件,該事件會返回一個 position,
監聽 pullUpLoad 事件,表示該事件觸發的時候是否上拉載入更多,
封裝一個滾動的方法 this.scroll.scrollTo(x,y,time),
封裝一個重新整理的方法 this.scroll.refresh(),會重新計算可滾動區域的高度,
封裝完成上拉載入更多的方法 this.scroll.finishPullUp
全域性外掛 Toast
- 專案中有兩個地方用到了彈窗元件,一個是點選加入購物車時,一個是點選結算時,如果想要使用彈窗元件那麼每次都需要匯入還要加一些程式碼,這樣做有點麻煩,如果有多個地方都需要使用這個彈窗元件那麼此時需要做的重複工作量太大,所以採用 Vue.use() 方法全域性註冊了一個外掛。
- 需要在 main.js 中 import, 然後 Vue.use() 進行註冊,Vue.use() 需要傳入至少一個引數,該引數只能是 Object 或 Function ,如果是 Object 那麼這個物件需要定義一個 install 方法,如果是 Funcion 那麼這個函式就被當做 install 方法,在 Vue.use() 執行時 install 方法會預設執行,install 方法呼叫時會將 Vue 作為引數傳入。Vue.use() 除了傳入第一個引數外,也可繼續傳入一個選項物件。這裡還要注意的是 Vue.use() 必須要在 new Vue() 之前被呼叫,這是因為在安裝元件時,元件給 Vue 新增全域性功能,所以必須寫在 new Vue() 之前,否則建立的 Vue 例項就無法獲取外掛新增的全域性功能。
- 所以我們在專案中使用了 Vue.use() 註冊了全域性外掛,在任何地方想使用只需要 this.$名稱 即可。
首頁
- 資料處理
banners 陣列儲存輪播圖的資料,recommends 陣列儲存推薦的資料。
還有其它的即需要展示的每一條商品的資料通過 goods 來儲存。
goods: {
'pop': {page: 0, list: [ ] },
'new': {page: 0, list: [ ] },
'sell': {page: 0, list: [ ] }
} - 最上面的輪播圖,可以根據圖片數量自動進行輪播,也可手動滑動圖片,是一個獨立的子元件。
輪播圖下面的推薦部分是一個獨立的子元件。 - 對 goods 中的資料進行展示,封裝 GoodsList 元件,而每一個商品又是一個獨立的小元件 GoodsListItem.vue。
- 點選選項卡進行資料的切換,監聽選項卡的點選事件,通過 $emit() 發出事件(攜帶 index),在父元件中監聽,根據 index 對應的資料型別(swicth(index)),父元件再通過 props 將 currentType 傳給 GoodsList 進行展示。
- 滑動過程中選項卡的吸頂效果,原理是複製一個選項卡(tabControl)在頂部,預設隱藏,通過 v-show 來決定何時出現,用一個變數儲存原選項卡 (contentControl) 的 offsetTop,在頁面滾動的過程中不斷獲取滾動的距離,當滾動的距離大於原選項卡 (contentControl) 的 offsetTop時,就顯示 tabControl。
- 上拉載入更多,Scroll 元件中的 pullingUp 一觸發就會傳送一個事件,父元件中進行監聽然後當事件觸發的時候去請求資料即可。
- 回到頂部元件 BackTop,預設不顯示,當頁面滾動到設定的距離時顯示。點選回到頂部需要監聽元件的點選(@click.native),然後觸發函式 scrollTo(x,y,time) 返回頂部。
詳情頁
- 當使用者點選商品時進入到詳情頁,首先要監聽每個 GoodsListItem 的點選,點選時進行路由的跳轉,並且攜帶商品的 id,根據 id 請求資料,再將資料進行展示。
- 底部功能欄的封裝也是一個獨立的子元件,當點選加入購物車時,將點選事件傳送給父元件 detail.vue,父元件中根據商品 id 獲取購物車(cart.vue,與 detail.vue 同級)中需要儲存的商品資料,提交到 vuex 進行全域性狀態管理,購物車元件中通過 $store.state 拿到資料進行渲染,加入購物車成功後彈出彈窗提示。
- 將商品新增到購物車其實有兩種情況,一種是 vuex 中還沒有這個商品的新增,另一種是 vuex 中已經有了這個商品,所以為了使 mutation 裡定義的函式職能單一,這裡將點選新增購物車這個操作提交到 action 中管理,再由 action 提交到 mutation 來使 vuex 管理的狀態進行更新。
- 點選聯動效果,商品詳情頁面中共有四個部分組成:商品,引數,評論,推薦,每個部分都是一個獨立的子元件,當資料請求完成時去獲得每個元件的 offsetTop ($refs.元件繫結的 ref 值.$el.offsetTop) 儲存在一個陣列中,監聽商品詳情欄中的點選,根據 index 觸發 scrollTo() 進行跳轉
- 滑動聯動效果,實時監聽滾動位置,通過和上面陣列中的值進行比較,在 0 - 引數之間的偏移這個高度時,currentIndex 為 0,在引數的偏移高度 - 評論的偏移高度時 currentIndex 為1,以此類推,這樣就可以實現在滑動的過程中與頂部標題資訊的聯動。
- 回到頂部,與首頁中的一樣。
購物車頁
- 渲染在詳情頁新增到購物車的資料,從 vuex 中拿到資料,封裝 CartList 元件,而每一條商品又是一個獨立的子元件 CartListItem。
- 底部工具欄的封裝,主要包括全選按鈕,選中商品的總價格,去結算時的彈窗。
- 全選按鈕的實現,某商品第一次新增到購物車時,需要給這件商品的資料裡定義一個是否選中 (checked)的屬性,預設為 true。為了管理選中的狀態,只能由 mucation 對狀態進行修改,商品最終是否展示也由 mucation 最終修改的狀態為準,監聽全選按鈕的點選,根據商品陣列中每個商品的 checked 屬性進行邏輯判斷即可。
- 點選去結算元件時,先判斷當前購物車的商品列表中是否有資料,如果沒有彈出提示資訊,讓使用者選中商品。
我的頁面
- 展示使用者的一些個人資訊,點選“我的購物車”跳轉到購物車頁面,this.$router.push('/cart')
專案中遇到的一些問題
由於引入 better-scroll 帶來的問題
- 引入 better-scroll 之後,帶來了一些問題,比如頁面會劃不動、scrollTo 跳不準、等等,導致這些問題的原因,多數情況下是因為請求的資料沒回來,或者拿到資料了但是頁面還未載入完,而 better-scroll 在這之前就需要計算可滾動區域的高度,但由於圖片還沒載入完所以這個時候計算的高度並不是最終的高度,所以導致滾動出現了問題,怎麼解決呢?
- 那麼這個時候可以在某些資源載入完成是時來個 scroll.refresh(),讓 better-scroll 重新計算可滾動區域的高度,比如專案中我們可以監聽圖片載入事件,當有圖片載入完成時就觸發 scroll.refresh()。但這樣的話只要一有圖片載入完成就會 scroll.refresh() ,使得 refresh() 的呼叫太頻繁了,在此我們可以進行一個防抖操作來減少 scroll.refresh() 的呼叫。
讓首頁的內容保持原來的位置
- 專案中在瀏覽到首頁某個位置時,使用者點選了商品然後進入了商品詳情頁或者進入了購物車等其它頁面,當再次回到首頁時發現不是之前瀏覽的位置。
- 可以在首頁的 deactivated() 中記錄下離開時的位置資訊,activated() 時再將位置設定為原來儲存的位置。
選項卡 tabControl 的吸頂效果
- 其實實現這個效果的基本思路就是首先獲取 tabControl 的 offsetTop ,怎麼獲取?this.$refs.tabControl.$el.offsetTop ,然後實時監測選項卡的滾動位置,當滾動位置達到 offsetTop 時將選項卡改為固定定位即可,這裡需要注意的是在 mounted() 中獲取的值不一定是正確的,因為可能頂部的輪播圖或者推薦部分的圖片沒有載入完,那麼如何獲取正確的值?監聽輪播圖的圖片載入情況,載入完畢發出事件,然後在首頁中再去獲取 offsetTop。
- 按照這樣的思路實現以後,發現並沒有達到預期的效果,而是出現了下面的商品部分會突然上移,並沒有實現停留的效果,所以換了另一種方案,就是上文提到的先在頂部複製一份佔位選項卡,預設不顯示,然後根據滾動位置和 offsetTop 來決定什麼時候顯示,這樣就實現了吸頂的效果。