Vuex存储相关信息

  1. 安装vuex

  2. vuex初始化(可能安装完就帮你初始化好了)

  3. 加入购物车相关逻辑

state定义一个cartList数组存储相关加入购物车的商品

1
2
3
state: {
cartList: []
}

vuex不建议我们在vuex外面去修改state的属性,所以我们在这里定义一个添加商品的相关mutation

方法思路如下

  1. 首先对已有的商品进行遍历,如果购物车已经有该商品,定义oldProduct接收该值(浅拷贝→引向同个内存地址)
  2. 如果该商品存在,则将其的数量加上一
  3. 如果没有,则定义一个该商品的数量属性,之后,存储至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的第一种用法
...mapGetters(['cartCount']),
//第二种用法
// ...mapGetters({
// length: 'cartLength',
// list: 'cartList'
// })
}

商品展示

创建子组件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>

CheckButton

在创建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)
}
}