8. 购物车
8.1 购物车基础结构
8.1.1 购物车列表结构
vue
<!-- 购物车列表 -->
<view class="cart-body">
<!-- 复选框组 -->
<van-checkbox-group :value="checkCart" @change="groupChange">
<!-- 滑动单元格 -->
<van-swipe-cell :right-width="toPx(100)" v-for="item in cart" :key="item.cartItemId">
<!-- 内容区域 -->
<view class="good-item">
<!-- 复选框 -->
<van-checkbox :name="item.cartItemId" />
<!-- 商品图片 -->
<view class="good-img"><image :src="item.goodsCoverImg" mode=""></image></view>
<!-- 商品信息 -->
<view class="good-desc">
<view class="good-title">
<text>{{ item.goodsName }}</text>
<text>x{{ item.goodsCount }}</text>
</view>
<view class="good-btn">
<view class="price">¥{{ item.sellingPrice }}</view>
<van-stepper :value="item.goodsCount" integer :min="1" :max="5" :data-id="item.cartItemId" @change="onChange" />
</view>
</view>
</view>
<!-- 右侧区域 -->
<template #right>
<van-button square icon="delete" type="danger" class="delete-button" @click="cartStore.removeCart(item.cartItemId)" />
</template>
</van-swipe-cell>
</van-checkbox-group>
</view>
使用复选框组控制复选框是否选中
滑动单元格 右侧宽度计算
js// rpx转px export const toPx = (rpx = 0) => { return Math.floor((uni.getWindowInfo().screenWidth / 750) * rpx) } // px转rpx export const toRpx = (px = 0) => { return Math.floor(750 * (px / uni.getWindowInfo().screenWidth)) }
8.1.2 购物车列表样式
scss
// 商品列表
.cart-body {
margin: 32rpx 0 0 0;
padding-bottom: 100rpx;
padding-left: 20rpx;
.good-item {
display: flex;
van-checkbox {
display: flex;
align-items: center;
}
.good-img {
image {
@include wd(200rpx);
}
}
.good-desc {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 40rpx;
.good-title {
@include fj();
}
.good-btn {
@include fj();
.price {
font-size: 32rpx;
color: red;
line-height: 56rpx;
}
}
}
}
.van-icon-delete {
font-size: 40rpx;
margin-top: 8rpx;
}
.delete-button button {
width: 100rpx;
height: 100%;
}
}
8.1.3 购物车空状态
vue
<!-- 购物车空状态 -->
<view class="empty" v-if="!cart.length">
<image class="empty-cart" src="https://s.yezgea02.com/1604028375097/empty-car.png" mode="widthFix"></image>
<view class="title">购物车空空如也</view>
<van-button round color="#1baeae" type="primary" @click="goTo" block>前往选购</van-button>
</view>
8.1.4 购物车空状态样式
scss
// 空状态
.empty {
width: 50%;
margin: 0 auto;
text-align: center;
margin-top: 300rpx;
.empty-cart {
width: 300rpx;
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
margin-bottom: 40rpx;
}
}
8.1.5 购物车操作栏
vue
<!-- 购物车操作栏 -->
<van-submit-bar v-if="cart.length" :price="total * 100" button-text="结算" @submit="onSubmit"><van-checkbox :value="checkAll" @change="allCheck">全选</van-checkbox></van-submit-bar>
8.1.6 购物车操作栏样式
scss
// 提交栏
.van-submit-bar {
border-top: 2rpx solid #ebedf0;
.van-checkbox {
margin-left: 20rpx;
}
.van-submit-bar__text {
margin-right: 20rpx;
}
.van-submit-bar__button {
.van-button {
background: $uni-primary;
border-color: $uni-primary;
}
}
}
// 单选框
.van-checkbox__icon--checked {
background-color: $uni-primary;
border-color: $uni-primary;
}
8.2 获取购物车列表
在pinia中处理购物车数据
shop.js
jsexport const CartStore = defineStore('CartStore', { // 定义数据 state: () => ({ // 购物车列表 cart: [], // 选中列表 checkCart: [], // 选中地址id addressId: '', }), // 函数 actions: { // 获取购物车列表【更新】 async updateCart() { // 1. 加载数据 const { data } = await getCart() // 2. 存储数据 this.cart = data // 3. 设置默认全选 this.checkCart = data.map(item => item.cartItemId + '') // 4. 判断当前是否为tabBar页面 【修改tabBar角标】 const pages = getCurrentPages() if (tabBarPage.includes(pages[pages.length - 1].route)) { uni.setTabBarBadge({ index: 2, text: this.count + '' }) } }, // 修改购物车数量【修改】 async changeCart(id, value) { // 1. 发起请求修改数据 await modifyCart({ cartItemId: id, goodsCount: value }) // 2. 修改本地数据 const index = this.cart.findIndex(item => item.cartItemId === id) this.cart[index].goodsCount = value }, // 删除购物车商品【删除】 async removeCart(id) { // 1. 发起请求 await deleteCartItem(id) // 2. 重新加载数据 this.updateCart() } }, // 计算属性 getters: { // 购物车数量 count: state => state.cart.length, // 是否全选 checkAll: state => state.cart.length === state.checkCart.length, // 商品合计 total: state => (state.cart .filter(item => state.checkCart.includes(item.cartItemId + '')) .reduce((sum, item) => sum + item.goodsCount * item.sellingPrice, 0)), // 选择商品ids cartItemIds: state => state.checkCart.join(',') }, persist: { // 开启持久化 enabled: true, }, })
获取数据
js// 页面显示时 onShow(() => { init() }) // 页面下拉时 onPullDownRefresh(() => { init() }) const init = async () => { // 判断是否登录【未登录就不加载】 if (!userStore.token) return // 1. 显示加载 uni.showLoading({ title: '加载中...', mask: true }) // 2. 加载数据 await cartStore.updateCart() // 3. 关闭加载 uni.hideLoading() uni.stopPullDownRefresh() }
8.3 添加至购物车
8.3.1 商品详情页添加操作
- 加入购物车
js
// 加入购物车
const handleAddCart = async () => {
await addCart({ goodsCount: 1, goodsId: state.goodsId })
uni.$Toast('添加成功')
// 更新购物车列表
cartStore.updateCart()
}
- 立即结算
js
// 立即结算
const payGoods = async () => {
// 1. 添加到购物车
await addCart({ goodsCount: 1, goodsId: state.goodsId })
// 2. 根据购物车Id生成订单(接口问题)
cartStore.updateCart()
// 3. 进入购物车下单
uni.switchTab({ url: '/pages/cart/cart' })
}
8.4 全选/反选购物车
复选框绑定修改事件
<van-checkbox-group :value="checkCart" @change="groupChange">
<van-checkbox :value="checkAll" @change="allCheck">全选</van-checkbox>
逻辑定义
js// * 商品选择 const groupChange = e => { cartStore.checkCart = e.detail }
js// * 全选商品 const allCheck = ({ detail }) => { cartStore.checkCart = detail ? cartStore.cart.map(item => item.cartItemId + '') : [] }
8.4 修改购物车数量
数量操作
<van-stepper :value="item.goodsCount" integer :min="1" :max="5" :data-id="item.cartItemId" @change="onChange" />
逻辑操作
js// * 商品数量改变 [value当前数量 id携带的参数] const onChange = async e => { const value = e.detail const { id } = e.target.dataset // 1. 限制最多5个 if (value > 5) return uni.$Toast('超出单个商品的最大购买数量') // 2. 限制最小1个 if (value < 1) return uni.$Toast('商品不得小于0') // 3. 提示修改 uni.showLoading({ title: '修改中...', mask: true }) // 4. 发起请求修改数据 await cartStore.changeCart(id, value) // 5. 关闭提示 uni.hideLoading() }
shop.js
js// 修改购物车数量【修改】 async changeCart(id, value) { // 1. 发起请求修改数据 await modifyCart({ cartItemId: id, goodsCount: value }) // 2. 修改本地数据 const index = this.cart.findIndex(item => item.cartItemId === id) this.cart[index].goodsCount = value }
shop.js
js// 计算属性 getters: { // 购物车数量 count: state => state.cart.length, // 是否全选 checkAll: state => state.cart.length === state.checkCart.length, // 商品合计 total: state => (state.cart .filter(item => state.checkCart.includes(item.cartItemId + '')) .reduce((sum, item) => sum + item.goodsCount * item.sellingPrice, 0)), // 选择商品ids cartItemIds: state => state.checkCart.join(',') },
8.5 删除购物车
右滑删除 绑定删除按钮事件
vue<!-- 右侧区域 --> <template #right> <van-button square icon="delete" type="danger" class="delete-button" @click="cartStore.removeCart(item.cartItemId)" /> </template>
删除逻辑
js// 删除购物车商品【删除】 async removeCart(id) { // 1. 发起请求 await deleteCartItem(id) // 2. 重新加载数据 this.updateCart() }
8.6 购物车tabBar角标
只在tabBar页面更新角标
js// 4. 判断当前是否为tabBar页面 【修改tabBar角标】 const pages = getCurrentPages() if (tabBarPage.includes(pages[pages.length - 1].route)) { uni.setTabBarBadge({ index: 2, text: this.count + '' }) }
定义为tabBar的页面
jsconst tabBarPage = ['pages/cart/cart', 'pages/my/my', 'pages/cate/cate', 'pages/home/home']
其他页面显示时 刷新角标
8.7 结算购物车
绑定事件
vue<van-submit-bar v-if="cart.length" :price="total * 100" button-text="结算" @submit="onSubmit"><van-checkbox :value="checkAll" @change="allCheck">全选</van-checkbox></van-submit-bar>
逻辑处理
js// * 提交订单 const onSubmit = e => { // 1. 校验是否有选择商品 if (cartStore.checkCart.length === 0) return uni.$Toast('请选择商品进行结算') // 2. 将选择商品ID组成字符串拼接 const ids = cartStore.checkCart.join(',') cartStore.addressId = '' uni.navigateTo({ url: '/subpkg/createOrder/createOrder' }) }