Skip to content
本页目录

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 获取购物车列表

  1. 在pinia中处理购物车数据

    shop.js

    js
    export 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,
    	},
    })
    
  2. 获取数据

    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 商品详情页添加操作

  1. 加入购物车
js
// 加入购物车
const handleAddCart = async () => {
	await addCart({ goodsCount: 1,  goodsId: state.goodsId })
	uni.$Toast('添加成功')
	// 更新购物车列表
	cartStore.updateCart()
}
  1. 立即结算
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 全选/反选购物车

  1. 复选框绑定修改事件

    <van-checkbox-group :value="checkCart" @change="groupChange">

    <van-checkbox :value="checkAll" @change="allCheck">全选</van-checkbox>

  2. 逻辑定义

    js
    // * 商品选择
    const groupChange = e => {
    	cartStore.checkCart = e.detail
    }
    
    js
    // * 全选商品
    const allCheck = ({ detail }) => {
    	cartStore.checkCart = detail ? cartStore.cart.map(item => item.cartItemId + '') : []
    }
    

8.4 修改购物车数量

  1. 数量操作

    <van-stepper :value="item.goodsCount" integer :min="1" :max="5" :data-id="item.cartItemId" @change="onChange" />

  2. 逻辑操作

    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 删除购物车

  1. 右滑删除 绑定删除按钮事件

    vue
    <!-- 右侧区域 -->
    <template #right>
        <van-button square icon="delete" type="danger" class="delete-button" @click="cartStore.removeCart(item.cartItemId)" />
    </template>
    
  2. 删除逻辑

    js
    // 删除购物车商品【删除】
    async removeCart(id) {
        // 1. 发起请求
        await deleteCartItem(id)
        // 2. 重新加载数据
        this.updateCart()
    }
    

8.6 购物车tabBar角标

  1. 只在tabBar页面更新角标

    js
    // 4. 判断当前是否为tabBar页面 【修改tabBar角标】
    const pages = getCurrentPages()
    if (tabBarPage.includes(pages[pages.length - 1].route)) {
        uni.setTabBarBadge({
            index: 2,
            text: this.count + ''
        })
    }
    
  2. 定义为tabBar的页面

    js
    const tabBarPage = ['pages/cart/cart', 'pages/my/my', 'pages/cate/cate', 'pages/home/home']
    
  3. 其他页面显示时 刷新角标

8.7 结算购物车

  1. 绑定事件

    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>
    
  2. 逻辑处理

    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' })
    }