说明:本总结来源于慕课网 @ustbhuangyi老师的课程《Vue.js2.5+cube-ui重构饿了么App》课程,本博客做了项目总结梳理便于回顾,需要学习的伙伴可以移步学习。与君共勉!
之前章节传送:
项目总结:vue.js2.5饿了么APP(1)概述+项目准备
项目总结:vue.js2.5饿了么APP(2)主要组件实现 - 头部相关组件
项目总结:vue.js2.5饿了么APP(3)主要组件实现 - 购物车相关组件(上)
购物车组件主要包含(shop-cart ,shop-control , shop-cart-list , shop-cart-sticky)4个部分,本节主要讲解后两个,以及一些购物车相关优化设计 。
速看
shop-cart-list组件+shop-cart-sticky组件 当购物车中有商品时,点击购物车会弹出购物车详细内容,列表限制高度并可以滚动。(起初一期时没有将这一部分抽离出来,因此就在shop-cart组件的后半部分,通过一个标记来控制区块显示)二期,对于这种弹层类组件选择使用cube-ui的<popup>组件实现,并将其设置为API调用,设置hide() show()方法来控制蒙层显示。
但是有个问题:shop-cart-list是API组件,会动态的向body中挂载结点,因此层级较高,高过内层组件层级。但是购物车组件是一个子组件,因此会遮盖到原来的购物车组件(这个组件有个向上突出的部分)。由于无法调整层级,解决方法就是:使用shop-cart-sticky组件。动态向body中挂载一个购物车组件的副本。
并且这一部分还要加入的功能有:(加入购物车的小球动画,结算,清空购物车)对于小球下落动画,可以调用cart-control的drop方法,两个弹出dialog的操作都使用了cube-ui中的dialog组件。而对于切换tab的购物车联动问题,只需要在请求数据之前判断是否已经拿到数据。
最后,对于header和shop-cart-list的弹层组件中都用到的hide() show() 方法用mixin抽离出来做代码简化。
目录
一、购物车详情shop-cart-list组件(二期)
1. 概述
当购物车中有商品时,点击购物车会弹出购物车详细内容,列表限制高度并可以滚动。(起初一期时没有将这一部分抽离出来,因此就在shop-cart组件的后半部分,通过一个标记来控制区块显示)二期,对于这种弹层类组件选择使用cube-ui的<popup>组件实现,并将其设置为API调用,设置hide() show()方法来控制蒙层显示。但是有个问题:shop-cart-list是API组件,会动态的向body中挂载结点,因此层级较高,高过内层组件层级。但是购物车组件是一个子组件,因此会遮盖到原来的购物车组件(这个组件有个向上突出的部分)。由于无法调整层级,解决方法就是:使用shop-cart-sticky组件。动态向body中挂载一个购物车组件的副本。
并且这一部分还要加入的功能有:(加入购物车的小球动画,结算,清空购物车)对于小球下落动画,可以调用cart-control的drop方法,两个弹出dialog的操作都使用了cube-ui中的dialog组件。而对于切换tab的购物车联动问题,只需要在请求数据之前判断是否已经拿到数据。
最后,对于header和shop-cart-list的弹层组件中都用到的hide() show() 方法用mixin抽离出来做代码简化。
2. 布局
当购物车中有商品时,点击购物车会弹出购物车详细内容。
布局:顶部是一个标题区+列表(有一个最大高度,超过最大高度列表可以滚动,如果列表高度不满足最大高度,只能被列表自身的高度撑高)
<transition name="fade">
<cube-popup>
<transition name="move" @after-leave="onLeave">
<div v-show="visible">
<div class="list-header">
<cube-scroll class="list-content" ref="listContent">
<ul>
<li class="food" v-for="food in selectFoods" :key="food.name">
<span class="name"></span>
<div class="price"></div>
<div class="cart-control-wrapper">
<cart-control @add="onAdd" :food="food"></cart-control>
</div>
</li>
</ul>
</cube-scroll>
</div>
</transition>
</cube-popup>
</transition>
使用cube-scroll组件实现列表滚动,并且其中添加shop-control组件做商品的购买。
使用cube-ui将购物车组件变成一个createAPI模块的调用,并使用cube-popup弹层组件
3. 实现
(1)popup组件使用
<cube-popup>组件是所有弹层组件的基础组件. position是弹窗出现的置,:mask-closable=true点击蒙层会派发click=maskclick事件,而达到关闭的效果,z-index弹窗高这里的type类似于class,可以改变样式(设置bottom样式达到我们的效果),并通过v-show控制弹层显隐,以及驱动过渡动画<transition>。
<cube-popup
v-show="visible"
:mask-closable=true
:z-index=90
position="bottom"
type="shop-cart-list"
@mask-click="maskClick">
</cube-popup>
(2)过渡动画
.cube-shop-cart-list
bottom: 48px
&.fade-enter, &.fade-leave-active
opacity: 0
&.fade-enter-active, &.fade-leave-active
transition: all 0.3s ease-in-out
.move-enter, .move-leave-active
transform: translate3d(0, 100%, 0)
.move-enter-active, .move-leave-active
transition: all 0.3 ease-in-out
(3)列表滚动
使用cube-scroll组件实现列表滚动,并且其中添加shop-control组件做商品的购买。
Data中添加visiable控制显示。提供两个方法show() hide()修改visiablemaskClick()方法控制其蒙层隐藏
(4)组件的使用
定义props有select Foods,从而使购物车中选择的商品通过props传递到shop-cart-list组件,
组件使用:将其变成API组件。 在register.js中修改,之后就可以通过API的方式调用。
import ShopCartList from 'components/shop-cart-list/shop-cart-list'
createAPI(Vue, ShopCartList)
(5)添加弹出触发方法
当我们点击底部购物车时会显示这个组件。在shop-cart的content中添加一个点击事件toggleList,并添加该方法。
(添加辅助变量listfold默认为收起状态)在函数中,只有是收起状态才会展开,并且判断商品件数是否为0,不为零显示lsit(_showShopCartList),为零隐藏list(_hidwShopCartList)
toggleList() {
if (this.listFold) {
if (!this.totalCount) {
return
}
this.listFold = false
this._showShopCartList()
this._showShopCartSticky()
} else {
this.listFold = true
this._hideShopCartList()
}
},
并且实现这两个私有方法,使用api调用组件(由于每次都是单例,因此做缓存)
_showShopCartList() {
this.shopCartListComp = this.shopCartListComp || this.$createShopCartList({
$props: {
selectFoods: 'selectFoods'
},
$events: {
hide: () => {
this.listFold = true
},
leave: () => {
this._hideShopCartSticky()
},
add: (el) => {
this.shopCartStickyComp.drop(el)
}
}
})
this.shopCartListComp.show()
},
_hideShopCartList() {
const comp = this.sticky ? this.$parent.list : this.shopCartListComp
comp.hide && comp.hide()
},
(6)存在的问题
但是有个问题就是,shop-cart-list是API组件,会动态的向body中挂载结点,因此层级较高,高过内层组件层级。但是购物车组件是一个子组件,因此会遮盖到原来的购物车组件。但是无法调整层级,
因此解决方法就是:使用shop-cart-sticky组件。动态向body中挂载一个购物车组件的副本。 shop-cart-sticky组件。
(7)商品购买时小球飞入动画
购物车小球,在shop-cat-list的cart-control点击需要向其父组件shop-cart组件派发onAdd()方法
<div class="cart-control-wrapper">
<cart-control @add="onAdd" :food="food"></cart-control>
</div>
onAdd(target) {
this.$emit(EVENT_ADD, target)
},
在shop-cart组件中监听该事件,在add中调用shop-cart-sticky组件的drop()方法,(其中需要在shop-cart-sticky中添加drop方法,并调用shop-cart的drop方法。)
_showShopCartList() {
this.shopCartListComp = this.shopCartListComp || this.$createShopCartList({
$props: {
selectFoods: 'selectFoods'
},
$events: {
hide: () => {
this.listFold = true
},
leave: () => {
this._hideShopCartSticky()
},
add: (el) => {
this.shopCartStickyComp.drop(el)
}
}
})
this.shopCartListComp.show()
},
(8)清空购物车
点击清空按钮,添加click事件empty(), 调用cube-ui的dialog,遍历food将所有的count设置为0
empty() {
this.$createDialog({
type: 'confirm',
content: '确认清空购物车吗?',
$events: {
confirm: () => {
this.selectFoods.forEach((food) => {
food.count = 0
})
this.hide()
}
}
}).show()
}
}
当商品数量为0时,也要收回list,因此在shop-cart组件中添加watch,对于totalcount,当展开情况,并且值为0,将其关闭。
watch: {
fold(newVal) {
this.listFold = newVal
},
totalCount(newVal) {
if (!this.listFold && !newVal) {
this._hideShopCartList()
}
}
},
(9)结算按钮
对于去结算按钮,添加点击事件pay(),使用cube-ui提供的dialog,当价格大于起送价,计算价格并显示
pay(e) {
if (this.totalPrice < this.minPrice) {
return
}
this.$createDialog({
title: '支付',
content: `您需要支付${this.totalPrice}元`
}).show()
e.stopPropagation()
},
(10)公共部分mixin抽离
所有我们使用的弹层组件都包含了show() hide() 主要用途是修改visible的true false,因此可以抽象出这部分代码,在common中添加mixins文件夹popup.js,在shop-cart-list等中添加引用mixin:[popupMixin]
const EVENT_SHOW = 'show'
const EVENT_HIDE = 'hide'
export default {
data() {
return {
visible: false
}
},
methods: {
show() {
this.visible = true
this.$emit(EVENT_SHOW)
},
hide() {
this.visible = false
this.$emit(EVENT_HIDE)
}
}
}
(11)滑动页面购物车关联
对于滑动页面也可以实现购物车的联动问题:在tab组件中我们定义了一个onChange()方法
onChange(current) {
this.index = current
const component = this.$refs.component[current]
component.fetch && component.fetch()
},
其中会执行组件的fetch方法,在goods组件中定义了这样一个fetch方法,每次都会getGoods,因此添加一个标记,只有当没有定义过时才会getGoods,如果已经获取过一次时就不用再次获取了。
fetch() {
if (!this.fetched) {
this.fetched = true
getGoods().then((goods) => {
this.goods = goods
})
}
},
二、购物车详情shopcart-list区块(一期)
1. 概述
一期实现没有很复杂的部分就是需要添加计算属性listShow,判断totalCount没有商品时将fold = true,否则为true。点击触发togglelist方法控制该部分显示。
2. 布局
组件部分没有抽离出来,而是直接在shop-cart组件中写入,放在整个组件的最下部。
<div class="shopcart-list" v-show="listShow" transition="fold">
<div class="list-header">
<h1 class="title">购物车</h1>
<span class="empty" @click="empty">清空</span>
</div>
<div class="list-content" v-el:list-content>
<ul>
<li class="food" v-for="food in selectFoods">
<span class="name">{{food.name}}</span>
<div class="price"><span>¥{{food.price*food.count}}</span></div>
<div class="cartcontrol-wrapper">
<cartcontrol :food="food"></cartcontrol>
</div>
</li>
</ul>
</div>
</div>
3. 实现
(1)实现组件显示隐藏
添加一个fold默认值为true,添加计算属性listShow。判断this.totalCount没有商品fold = true,否则为true。
listShow() {
if (!this.totalCount) {
this.fold = true;
return false;
}
let show = !this.fold;
if (show) {
this.$nextTick(() => {
if (!this.scroll) {
this.scroll = new BScroll(this.$els.listContent, {
click: true
});
} else {
this.scroll.refresh();
}
});
}
return show;
}
},
在点击content时触发事件togglelist,并实现方法,当点击list-mask可以收回shop-cart,添加事件listShow事件
toggleList() {
if (!this.totalCount) {
return;
}
this.fold = !this.fold;
},
(2)实现动画
动画:向上滑动,添加transition=fold,此处点击事件是better-scroll组件派发的事件。
&.fold-transition
transition: all 0.5s
transform: translate3d(0, -100%, 0)
&.fold-enter, &.fold-leave
transform: translate3d(0, 0, 0)
(3)蒙层的实现
该组件出现时背后是一个蒙层,因此在shop-cart组件同级下定义list-mask
<div class="list-mask" @click="hideList" v-show="listShow" transition="fade"></div>
并添加渐变效果和样式。模糊效果使用 backdrop-filter: blur(10px)
.list-mask
position: fixed
top: 0
left: 0
width: 100%
height: 100%
z-index: 40
backdrop-filter: blur(10px)
&.fade-transition
transition: all 0.5s
opacity: 1
background: rgba(7, 17, 27, 0.6)
&.fade-enter, &.fade-leave
opacity: 0
background: rgba(7, 17, 27, 0)
三、shop-cart-sticky组件
1. 概括
shop-cart-list是API组件,会动态的向body中挂载结点,因此层级较高,高过内层组件层级。但是购物车组件是一个子组件,因此会遮盖到原来的购物车组件。但是无法调整层级,因此解决方法就是:使用shop-cart-sticky组件。动态向body中挂载一个购物车组件的副本。而Sticky组件就是对chop-cart组件的简单包装。
2. 布局
Sticky组件就是对chop-cart组件的包装,把它需要的数据通过props传入,因此可以把sticky组件作为API调用就可以了。并且也包含hide() show()函数控制visible显隐。
<div class="shop-cart-sticky" v-show="visible">
<shop-cart
ref="shopCart"
:selectFoods="selectFoods"
:deliveryPrice="deliveryPrice"
:minPrice="minPrice"
:fold="fold"
:sticky=true>
</shop-cart>
</div>
3. 实现
sticky组件要可以跟着页面的切换而保存,因此调用要添加在shop-cart组件的toggleList函数中(如上代码)并实现该私有函数,并接收props
_showShopCartSticky() {
this.shopCartStickyComp = this.shopCartStickyComp || this.$createShopCartSticky({
$props: {
selectFoods: 'selectFoods',
deliveryPrice: 'deliveryPrice',
minPrice: 'minPrice',
fold: 'listFold',
list: this.shopCartListComp
}
})
this.shopCartStickyComp.show()
},
_hideShopCartSticky() {
this.shopCartStickyComp.hide()
}
四、createAPI详解
注:购物车部分还有一些问题的解决还没有整理出来(特别是sticky部分)之后还会细致整理。