Vuex存储相关信息
安装vuex
vuex初始化(可能安装完就帮你初始化好了)
加入购物车相关逻辑
state定义一个cartList数组存储相关加入购物车的商品
1 2 3
| state: { cartList: [] }
|
vuex不建议我们在vuex外面去修改state的属性,所以我们在这里定义一个添加商品的相关mutation
方法思路如下
- 首先对已有的商品进行遍历,如果购物车已经有该商品,定义oldProduct接收该值(浅拷贝→引向同个内存地址)
- 如果该商品存在,则将其的数量加上一
- 如果没有,则定义一个该商品的数量属性,之后,存储至cartList中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| mutations: { addCart(context, payload) { let oldProduct = null for (let item of context.state.cartList) { if (item.iid === payload.iid) { oldProduct = item } } if (oldProduct) { oldProduct.count += 1 }else { payload.count = 1 context.state.cartList.push(payload) } } }
|
但是,不建议在一个mutations中实现这么多个方法,也就是一个mutations只要对应一个方法就可,所以,我们将复杂的判断逻辑引到action中去(action不仅可以实现异步操作,同时也可以在里面实现复杂的逻辑判断),同时,我们用find()
函数代替for遍历,简化代码,重构代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| mutations: { addCounter(state,payload) { payload.count += 1 }, addToCart(state,payload) { state.cartList.push(payload) } }, actions: { addCart(context, payload) { let oldProduct = context.state.cartList.find(item => item.iid === payload.iid)
if (oldProduct) { context.commit('addCounter', oldProduct) } else { payload.count = 1 context.commit('addToCart', payload) } } },
|
最后再进行文件分离,即将actions、mutations分离出单独的文件出来
购物车导航条
没啥可说,引入组件并进行相应修改,重点在于获取当前购物车的商品种类数量
mapGetters
mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
也就是说,我们可以直接通过mapgetters得到vuex中的getter,并在computed中使用
定义vuex的getter,同时跟之前action、mutation一样将其分离出来
1 2 3 4 5 6 7 8 9 10
| const getters = { cartList(state) { return state.cartList }, cartCount(state, getters) { return getters.cartList.length } }
export default getters
|
在ShopCart组件中使用
首先导入mapGetters
1
| import { mapGetters } from 'vuex'
|
使用非常简单,类似解构赋值去使用getter
1 2 3 4 5 6 7 8 9
| computed: { ...mapGetters(['cartCount']), }
|
商品展示
创建子组件CartList.vue,主要用来包裹每个商品(CartListItem),同时引用scroll组件(注意scroll的高度设置)
并创建CartListItem,引入getters的cartList,使用for遍历循环展示每个商品
这里要注意,每次新加入商品,CartList都要在activated中做一次scroll刷新,否则高度固定,新加入商品,你就会发现拉不下去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div class="cart-list"> <scroll class="wrapper" ref="scroll"> <div> <cart-list-item v-for="item in cartList" :key="item.iid" :item-info="item"></cart-list-item> </div> </scroll> </div> </template>
<script> import Scroll from 'components/common/scroll/scroll'; import CartListItem from './CartListItem'; import { mapGetters } from 'vuex';
export default { name: "CartList", components: { Scroll, CartListItem }, computed:{ ...mapGetters(['cartList']), }, activated(){ this.$refs.scroll.refresh(); } } </script>
<style scoped> .cart_list { height: calc(100% - 44px -49px - 50px); } .wrapper { height: 100%; overflow: hidden; } </style>
|
在创建CartListItem前,我们需要创建checkButton组件用来执行选中商品的相关操作
该组件主要问题就是选中时的样式和未选中的样式切换问题
我们定义一个prop用来决定选中与否样式的切换,并由引用该组件决定是否选中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <template> <div class="check-button"> <div class="icon-selector" :class="{'selector-active': isChecked}"> <img src="~/assets/img/cart/tick.svg" alt=""> </div> </div> </template>
<script> export default { name: "CheckButton", props: { isChecked: { type: Boolean, default: false, } } } </script>
<style scoped> .icon-selector { position: relative; margin: 0; width: 18px; height: 18px; border-radius: 50%; border: 2px solid #ccc; cursor: pointer; }
.selector-active { background-color: #ff8198; border-color: #ff8198; } </style>
|
CartListItem
样式相关的不多讲,根据需求慢慢调,下面主要讲相关的逻辑实现
商品展示
props接收父组件传来的商品数据
1 2 3 4 5 6
| props: { itemInfo: Object, default(){ return {} } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div id="shop-item"> <div class="item-selector"> <check-button></check-button> </div> <div class="item-img"> <img :src="showImage" alt="商品图片"> </div> <div class="item-info"> <div class="item-title">{{itemInfo.title}}</div> <div class="item-desc">商品描述: {{itemInfo.desc}}</div> <div class="info-bottom"> <div class="item-price left">¥{{itemInfo.price}}</div> <div class="item-count right">x{{itemInfo.count}}</div> </div> </div> </div>
|
根据数据的对象模型判断商品是否选中
首先,在vuex的mutations中记录商品的选中状态
1 2 3 4 5
| addToCart(state,payload) { payload.checked = true state.cartList.push(payload) }
|
在CartListItem绑定状态同时注册点击事件checkedChange
1
| <check-button :is-checked="itemInfo.checked" @click.native="checkedChange"></check-button>
|
点击状态改变(直接取个反就ok)
1 2 3 4 5
| methods: { checkedChange() { this.itemInfo.checked = !this.itemInfo.checked } }
|
购物车底部栏CartBottomBar
依旧是先创建组件并引入以及相关样式修改然后就是将checkButton引入当作全选按钮
选中商品总价
这里要计算出当前选中的商品的总价,所以使用filter做商品过滤之后再用reduce做价格总和,在computed中进行
1 2 3 4 5
| totalPrice() { return '¥' + this.$store.state.cartList.filter(item => item.checked).reduce((preValue, item) => { return preValue + item.count * item.price }, 0).toFixed(2) }
|
当前选中商品种类数量
同样的方式对选中的商品进行总数计算
1 2 3
| checkLength() { return this.cartList.filter(item => item.checked).length }
|
显示全选状态
首先绑定CheckButton组件的选中状态
1
| <CheckButton class="select-all" :isChecked="isSelectAll"></CheckButton>
|
这里实现的思路主要是:只要有一个商品没选中,那么当前CheckButton的状态就是未选中,所以这里对cartList进行过滤,只要有一个商品未选中,那么该方法总会返回一个至少为1的值,取反之后则为false(除了0其他数字皆为true),则当前为未全选状态,当然你也可以使用find()函数或者for循环完成该逻辑
1 2 3 4
| isSelectAll() { return !this.$store.state.cartList.filter(item => item.checked === false).length }
|
点击全选与否切换
绑定CheckButton的点击事件
1
| <CheckButton class="select-all" :isChecked="isSelectAll" @click.native = "checkClick"></CheckButton>
|
然后实现点击切换全选与否
主要对上面的isSelectAll计算属性进行判断,若为全选状态,则对所有商品进行遍历将选择状态改为false,否则,执行相反操作
1 2 3 4 5 6 7
| checkClick() { if (this.isSelectAll) { this.$store.state.cartList.forEach(item => item.checked = false) } else { this.$store.state.cartList.forEach(item => item.checked = true) } }
|