在學習了 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 來決定什么時候顯示,這樣就實現了吸頂的效果。