最近在写一个小程序,其中设计到了购买商品的业务
我们知道,像美团和饿了么,还有淘宝京东这些知名app 都是有购物车的功能
下面我讲一下我实现的思路
=> 使用Vuex
=> 在添加删除时触发对应方法
=> 判断是加还是减,并把数据做逻辑处理储存
=> 触发Vuex内的方法,去修改或储存item数据
=> 渲染到购物车的列表从Vuex内去取
封装组件
因为不止在一个地方使用到了购物车,所以我选择封装一个组件,可以进行复用
<template>
<view>
<view class="my-cart-mask" v-if="myShow" @click="hide"></view>
<view class="my-cart animated" v-if="myShow" :class="{slideInUp: wrapper,slideOutDown: !wrapper}">
<view class="title-wrapper">
<view class="title">购物车</view>
<view class="iconfont close" @click="hide"></view>
</view>
<view class="list-wrapper" v-if="list.length">
<view class="cart-list">
<view v-for="(item, index) in list" :key="index" class="cart-item">
<cartItem :info="item"></cartItem>
</view>
</view>
<view class="price-wrapper">
<view class="price-item">包装费<text>¥4</text></view>
<view class="price-item">配送费<text>¥4</text></view>
<view class="price-item">快递费<text>¥4</text></view>
</view>
</view>
<view class="empty" v-else>您的购物车是空的</view>
</view>
</view>
</template>
<script>
import cartItem from '../../components/cart_item';
const app = getApp();
export default {
props: {
show: {
type: Boolean,
default: false
},
info: {
type: Object,
default() {
return {};
}
},
list: {
type: Array,
default() {
return [];
}
}
},
data () {
return {
mask: false,
wrapper: false,
myShow: false,
};
},
mounted() {
},
watch: {
show(isShow) {
if (isShow) {
this.myShow = isShow;
this.wrapper = true;
} else {
this.wrapper = false;
setTimeout(() => {
this.myShow = false;
}, 400);
}
},
},
methods: {
hide() {
this.wrapper = false;
setTimeout(() => {
this.$emit('handleHide', {
show: false
});
}, 400);
},
},
components: {
cartItem
},
};
</script>
<style lang="scss" scoped>
.my-cart-mask {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 5;
background: rgba(28,28,28,0.2);
}
.my-cart {
position: fixed;
left: 0;
right: 0;
bottom: 110rpx;
z-index: 6;
background: #fff;
max-height: 80vh;
padding: 8rpx 30rpx 32rpx;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
border-radius: 20rpx 20rpx 0rpx 0rpx;
.title-wrapper {
height: 44rpx;
display: flex;
align-items: center;
position: relative;
padding-top: 38rpx;
.title {
font-size: 32rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
}
.close {
position: absolute;
right: 0rpx;
top: 44rpx;
&:before {
font-size: 30rpx;
color: #000;
content: '\eaf2';
}
}
}
.list-wrapper {
.cart-list {
max-height: calc(80vh - 156rpx);
overflow: scroll;
}
.price-wrapper {
height: 40rpx;
display: flex;
align-items: center;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 600;
color: #666666;
.price-item {
margin-right: 58rpx;
text {
font-family: PingFangSC-Semibold;
margin-left: 16rpx;
}
}
}
}
.empty {
padding: 20rpx;
text-align: center;
font-size: 28rpx;
color: #333;
}
}
</style>
在父组件内去使用
<myCart :show="showCart" :info="info" @handleHide="showCart = false" :list="menuList"></myCart>
showCart 是去控制显示隐藏的
所以在初始化的时候就在data 内去定义的是 showCart: false
当我们点击关闭的时候 转变为false 点击特定按钮(更多)的时候 就转换成true
传入的值 list
list 是一个数组对象 是我们获取的一个商品列表,意思就是我们点击了 + 的商品 都添加到了 list 内去了
所以在这里就是把list 做循环 然后又引入了一个组件 把每一个item都传入进去 这个组件是渲染单个商品的
<cartItem :info="item"></cartItem>
<template>
<view class="container">
<view class="logo">
<image :src="info.img"></image>
</view>
<view class="goods-info">
<view class="name">{{info.name}}</view>
<view class="price">¥{{info.fixPrice}}</view>
</view>
<view class="operate">
<addCart :info="info" @handleOperate="handleOperate"></addCart>
</view>
</view>
</template>
<script>
import addCart from './add_cart';
const app = getApp();
export default {
props: {
info: {
type: Object,
default() {
return {};
}
}
},
data () {
return {
};
},
mounted() {
},
methods: {
handleOperate (index,item) {
if(index == -1) {
this.$store.commit('reduceFoods', item)
} else if (index == 1) {
this.$store.commit('addFoods', item)
}
},
},
components: {
addCart
},
};
</script>
<style lang="scss" scoped>
.container {
display: flex;
align-items: center;
padding-top: 30rpx;
.logo {
margin-right: 20rpx;
image {
width: 168rpx;
height: 118rpx;
border-radius: 20rpx;
}
}
.goods-info {
flex: 1;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 600;
line-height: 40rpx;
color: #666666;
.name {
padding-bottom: 10rpx;
}
}
}
</style>
这里也是引入了 addCart 就是用于加减商品的一个组件 这个组件也是需要去封装
使用Vuex状态管理库
首先就是下载和引入 这里不做多的讲解
这里只做展示 我都存到了 store/index 内去了
首先就是在Vuex内的mutations 内去定义方法
// 加
addFoods(state, item){
let index = state.foodList.findIndex(v => v.id == item.id);
if (index == -1) {
item.cartNum = 1;
state.foodList.push(item);
} else {
state.foodList[index].cartNum++;
};
},
// 减
reduceFoods(state, item){
let index = state.foodList.findIndex(v => v.id == item.id);
state.foodList[index].cartNum--;
},
这里的加 我是去通过id去判断list内有没有这个商品 有就++ 没有就push
当然 是用的findIndex 去查找 如果没找到就返回的是 -1
减法就不用处理这种情况 因为数量到1时可以隐藏 - 的按钮 不让用户去 - 当然也可以写类似的判断 但是会更麻烦一点
减的时候也是通过 findIndex 去判断index 也就是商品在list 的位置 从而去--
在加减组件内定义处理逻辑
在addCart 也就是加减模块的里面 给 + 和 - 都绑定事件
@click="handleOperate(-1,info)"
通过传第一个 参数 去判断是+ 还是 -
第二个参数就是传点击的这个商品的信息
由于存Vuex的代码是写在父组件内 所以需要用$emit 去调用方法(需要在父组件内的子组件上绑定)
// 子组件
handleOperate (index, item) {
this.$emit('handleOperate', index, item);
}
// 父组件
<addCart :info="item" @handleOperate="handleOperate"></addCart>
handleOperate (index,item) {
// console.log(index);
if(index == -1) {
this.$store.commit('reduceFoods', item)
} else if (index == 1) {
this.$store.commit('addFoods', item)
}
this.menuList = this.$store.state.foodList
},
在这里做了判断 如果是- 就调用Vuex内的做减的方法
如果是+就是调用加的方法,并且把对应的商品信息也是传递了过去