五宣蔚、實(shí)現(xiàn)購(gòu)物車(chē)頁(yè)面功能
5.1 完成購(gòu)物車(chē)頂部導(dǎo)航的功能
- 在購(gòu)物頁(yè)面引入頂部導(dǎo)航組件
Navbar.vue
注冊(cè)和使用 - 由于頂部導(dǎo)航只有中間部分顯示信息,因此只需要使用中間插槽部分
- 在頂部導(dǎo)航顯示購(gòu)物商品數(shù)量
- 使用 vuex 中的
getters
方法將cartList
數(shù)據(jù)進(jìn)行計(jì)算長(zhǎng)度后返回 - 將返回的長(zhǎng)度存儲(chǔ)到
cartListLength
中 - 再在購(gòu)物車(chē)組件
Cart.vue
中的computed
中拿到getter
中的cartListLength
返回給cartLength
- 再在頁(yè)面上渲染商品數(shù)量的數(shù)據(jù)
{{cartLength}}
- 使用 vuex 中的
- 完成頂部導(dǎo)航的樣式
5.2 使用 mapGetters 函數(shù)改造5.1中的內(nèi)容
- 抽離
getters
中的內(nèi)容到getters.js
中- 獲取存儲(chǔ)到
state
中cartList
的數(shù)組長(zhǎng)度cartLength
- 獲取存儲(chǔ)到
state
中的商品數(shù)據(jù)cartList
- 獲取存儲(chǔ)到
- 在
Cart
組件中引入mapGetters
import { mapGetters } from 'vuex'
- 使用對(duì)象展開(kāi)運(yùn)算符將 getter 混入 computed 對(duì)象中
computed: { ...mapGetters(["cartLength"]) }
5.3 完成購(gòu)物車(chē)列表商品卡片
- 使用 Vant 組件中的
Card
橙凳,Checkbox
,CheckboxGroup
來(lái)實(shí)現(xiàn)完整商品卡片 - 創(chuàng)建商品列表組件
CartList.vue
并將其引入Cart
父組件中,注冊(cè)并使用 - 在
CartList
組件中使用mapGetters
來(lái)獲取 vuex 中的商品數(shù)據(jù) - 用
CheckboxGroup
組件將Checkbox
和Card
包裹在一起,使用v-for
進(jìn)行遍歷來(lái)顯示每個(gè)商品卡片横殴,實(shí)現(xiàn)可選擇的商品卡片 - 修改并調(diào)試樣式,使其滿(mǎn)足頁(yè)面布局
- 實(shí)現(xiàn)商品列表的滾動(dòng)區(qū)域妇智,引入
Scroll
組件滥玷,來(lái)讓替換原生滾動(dòng)-
CartList
組件中將scroll
的父標(biāo)簽上設(shè)置高度 - 再在
scroll
標(biāo)簽上設(shè)置滾動(dòng)范圍氏身,即外層高度減去頭部和底部的高度 - 由于添加了購(gòu)物車(chē)數(shù)據(jù)后可滾動(dòng)區(qū)域的高度發(fā)生了變化巍棱,因此需要調(diào)用已
scroll
的刷新activated() { this.$refs.scroll.refresh(); },
- 由于使用了
keep-alive
保持狀態(tài)的功能,需要在activated
生命周期函數(shù)中去調(diào)用該刷新方法蛋欣,這樣在每次進(jìn)入購(gòu)物車(chē)頁(yè)面時(shí)航徙,由于滾動(dòng)區(qū)域高度有變化重新刷新計(jì)算一下
-
5.4 實(shí)現(xiàn)添加購(gòu)物車(chē)商品時(shí),已經(jīng)存在的商品自動(dòng)加一
- 先查找之前的購(gòu)物車(chē)列表中是否有該商品
- 使用
find
函數(shù)查找cartList
中與商品iid
相符的數(shù)據(jù)陷虎,并返回該商品信息let oldProduct = state.cartList.find(item => item.iid === payload.iid)
- 使用
- 然后判斷
oldProduct
是否為空到踏,即oldProduct
是否為true
- 使用
if else
來(lái)判斷,當(dāng)oldProduct
為true
時(shí)尚猿,oldProduct.count +=1
- 否則
payload.count = 1
窝稿,并往cartList
中插入一條新的商品數(shù)據(jù),并且該商品中帶有count
屬性if (oldProduct){ oldProduct.count += 1 } else { payload.count = 1 state.cartList.push(payload) }
- 使用
- 對(duì) vuex 中的 store 進(jìn)行重構(gòu)
-
mutations
中的方法盡可能完成單一的事件 -
actions
中來(lái)完成判斷邏輯復(fù)雜和異步等操作- 在添加購(gòu)物車(chē)時(shí)凿掂,采用
dispatch()
方法來(lái)發(fā)送操作 - 將原本
mutations
中的addToCartList
方法放到actions
中 - 而且接受一個(gè)與
store
實(shí)例具有相同方法和屬性的context
對(duì)象 - 因此將 if 判斷邏輯中的加1操作和push操作通過(guò)
commit
提交
mutations: { addCounter(state, payload){ payload.count++ }, addToCart(state, payload){ state.cartList.push(payload) } } actions: { addToCartList(context, payload){ let oldProduct = constext.state.cartList.find(item => item.iid === payload.iid) if (oldProduct){ context.commit("addCounter", oldProduct) } else { payload.count = 1 context.commit("addToCart", payload) } } }
- 在添加購(gòu)物車(chē)時(shí)凿掂,采用
- 將
mutations
中的內(nèi)容進(jìn)行抽離放到mutations.js
文件中 - 將
actions
中的內(nèi)容進(jìn)行抽離放到actions.js
文件中
-
5.5 完成底部提交商品內(nèi)容
- 創(chuàng)建組件
CartBottomBar
組件伴榔,并在父組件購(gòu)物車(chē)CartList
中引入、注冊(cè)和使用 - 使用UI Vant 組件中的
SubmitBar
提交訂單欄組件- 先在
main.js
中引入SubmitBar
和使用 - 再在
CartBottomBar
組件中使用<van-submit-bar/>
組件庄萎,并且其中包裹<van-checkbox/>
用來(lái)作為全選按鈕
[SubmitBar 提交訂單欄]: https://youzan.github.io/vant/#/zh-CN/submit-bar#gao-ji-yong-fa - 調(diào)整其組件樣式
- 先在
- 將添加到購(gòu)物車(chē)的商品價(jià)格計(jì)算總數(shù)顯示在
CartBottomBar
組件上的:price
中- 在父組件
CartList
中的計(jì)算屬性computed
中計(jì)算并存儲(chǔ)總價(jià)格totalPrice
- 此處通過(guò) reduce 計(jì)算累加踪少,返回一個(gè)累加函數(shù)的結(jié)果
- 注意:由于
reduce
對(duì)空數(shù)組不執(zhí)行回調(diào),當(dāng)result數(shù)組為空時(shí)糠涛,會(huì)報(bào)錯(cuò) - 因此給
result
數(shù)組一個(gè)初始值0data() { return { result: [0] }; }, computed: { totalPrice() { // 此處通過(guò) reduce 計(jì)算累加援奢,返回一個(gè)累加函數(shù)的結(jié)果 // 注意:由于 reduce 對(duì)空數(shù)組不執(zhí)行回調(diào),當(dāng)result數(shù)組為空時(shí)忍捡,會(huì)報(bào)錯(cuò) return this.result.reduce((preValue, item) => { return preValue + item.price * item.count; }); } },
- 再將
totalPrice
通過(guò)父子組件傳值的方式傳給CartBottomBar
的props
中的totalPrice
- 最后需要將
totalPrice100
給到price中:price="totalPrice100"
- 在父組件
- 實(shí)現(xiàn)全選反選各種場(chǎng)景功能
-
全選按鈕場(chǎng)景分析:
- 全選按鈕為選中時(shí)集漾,所有商品全部選中
- 當(dāng)商品全部選中時(shí)切黔,全選按鈕自動(dòng)選中
- 全選中后,再次點(diǎn)擊全選按鈕帆竹,所有商品取消選中
在子組件的全選按鈕上綁定
checkAll
方法绕娘,將其發(fā)送給父組件CartList
-
再在父組件的
<cart-bottom-bar/>
上綁定發(fā)送過(guò)來(lái)的事件checkAllChange
,通過(guò)該事件方法觸發(fā)全選和反選效果(實(shí)現(xiàn)了場(chǎng)景1栽连、3)checkAllChange() { // 通過(guò)判斷 result 數(shù)組的長(zhǎng)度與 cartList 數(shù)組的長(zhǎng)度是否一致來(lái)進(jìn)行取反 if (this.result.length < this.cartList.length) { this.$refs.checkboxGroup.toggleAll(true); } else { this.$refs.checkboxGroup.toggleAll(); } }
-
在計(jì)算屬性
isTotalchecked
中判斷险领,當(dāng)商品全部選中時(shí),將isTotalchecked
傳遞給子組件<cart-bottom-bar/>
的props
中的totalChecked
秒紧,并在復(fù)選框的v-model
指令上使用(實(shí)現(xiàn)了場(chǎng)景2)// 判斷當(dāng)商品一一勾選后绢陌,全選按鈕自動(dòng)勾選 getTotalChecked() { return this.result.length === this.cartList.length && this.result.length > 0 ? true : false; },
-
注意: 1)如果將子組件中
props
的totalChecked
直接在v-model
指令上使用會(huì)出現(xiàn)(第一個(gè)vue的告警),雖然不影響功能
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "totalChecked"
2) 因此需要采用計(jì)算屬性
computed
將totalChecked
的值返回給一個(gè)新屬性值_totalChecked
在在v-model
中使用_totalChecked() { return this.totalChecked; }
3)但是這又會(huì)產(chǎn)生新(第二個(gè) vue 的告警)
[Vue warn]: Computed property "_totalChecked" was assigned to but it has no setter.
4)因此需要給_totalChecked
設(shè)置set
熔恢,之后再在v-model
中引用就不會(huì)保存_totalChecked: { get: function() { return this.totalChecked; }, set: function() {} }
-
- 改造第3步中的計(jì)算已勾選商品的價(jià)格總數(shù)
getTotalPrice() { let arr = this.result; let total = 0; if (arr.length === 0) return 0; for (let j = 0; j < arr.length; j++) { total += arr[j].price * arr[j].count; } return total; },
- 并將計(jì)算的結(jié)果返回給
totalPrice
計(jì)算屬性 - 通過(guò)屬性綁定父子組件傳值的方式脐湾,傳遞到子組件的
totalPrice
中,并在界面渲染
- 并將計(jì)算的結(jié)果返回給
- 上面的第 4 步使用另外一種方式避免出現(xiàn)第一個(gè)vue告警的情況:
- 將
CartBottomBar
組件內(nèi)的代碼直接寫(xiě)在父組件CartList
中叙淌,就會(huì)避免采用父子組件的傳值方式秤掌,也就不會(huì)出現(xiàn)直接使用props
中的totalChecked
而導(dǎo)致的第一個(gè)vue告警。 - 但第二個(gè)告警任然會(huì)出現(xiàn)鹰霍,不過(guò)只需要像注意事項(xiàng)的第 4) 條中設(shè)置 Set 就可以了闻鉴。
- 將
5.6 優(yōu)化添加購(gòu)物車(chē)方法,并引入提示
- 添加購(gòu)物車(chē)成功后要有
toast
提示茂洒,因此需要進(jìn)行異步回調(diào)孟岛,來(lái)提示不同的內(nèi)容 - 在
addToCartList
方法中使用Promise
函數(shù)進(jìn)行回調(diào)- 當(dāng)添加商品后,若是新增商品則回調(diào)
resolve
('添加新的商品成功') - 若是只是商品 +1 則回調(diào)
resolve
('當(dāng)前的商品數(shù)量+1')addToCartList(context, payload) { return new Promise((resolve, reject) => { // 2. 先查找之前的購(gòu)物車(chē)列表中是否有該商品 let oldProduct = context.state.cartList.find(item => item.iid === payload.iid) // 3. 然后判斷 oldProduct 是否為空督勺,即 oldProduct 是否為 true渠羞, // 不為空就將原本商品的數(shù)量加1,為空就往 cartList 插入一條帶有 count = 1 屬性的新的數(shù)據(jù)智哀, if (oldProduct) { context.commit("addCounter", oldProduct) resolve('當(dāng)前的商品數(shù)量+1') } else { payload.count = 1 context.commit("addToCart", payload) resolve('添加新的商品成功') } }) }
- 當(dāng)添加商品后,若是新增商品則回調(diào)
- 在
Detail
組件中的addToCart
方法中對(duì)dispatch
進(jìn)行回調(diào)的內(nèi)容用彈窗Toast
提示
注意: 引入this.$store.dispatch("addToCartList", products).then(res => { Toast.success(res); });
Toast
組件時(shí)次询,若已經(jīng)在main.js
中已經(jīng)引入,但直接使用任然會(huì)報(bào)錯(cuò)瓷叫。因此需要在當(dāng)前組件中再引入一次
5.7 優(yōu)化圖片懶加載的功能
- 需要使用
vue-lazyload
組件 - 引入組件
VueLazyload
并使用import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload, { loading: require('./assets/img/common/placeholder.png') })
- 在組件
GoodsItem
組件中的圖片標(biāo)簽中使用v-lazy
指令屯吊,這樣就可以使用懶加載的圖片了