Bootstrap

uniapp小程序购物车多商家单选全选功能

效果图如下:
说明:点击商家复选框,可选中当前商家下的所有商品。点击全选,选中全部商家的商品
在这里插入图片描述
以下是代码:

<template>
	<view class="cart-index">
		<view class="lement">
			<view>
				<checkbox-group @change="checkboxChangeAll">
					<checkbox class="round" style="transform:scale(0.7)" :checked="isAllChecked" />
				</checkbox-group>
			</view>
			<view>全选</view>
			<view style="display: flex;">
				<view>合计:</view>
				<view style="color: #FF1E1E;font-size: 28rpx;">{{totalPrice}}</view>
			</view>
			<view>免费配送</view>
			<view class="now" @click="submitOrder">立即结算</view>
		</view>
		<!-- 这里开始遍历商家和商品 -->
		<!-- 这一层是商家,val是商家的数据,k是商家的下标,可以在数组中用于定位商家 -->
		<view class="item" v-for="(val,k) in cartData" :key="k">
			<view class="title">
				<checkbox-group @change="checkboxChangeShop($event, k)">
					<checkbox class="round" style="transform:scale(0.7)" :checked="val.checked" />
				</checkbox-group>
				<image style="width:35rpx;height: 35rpx;margin-top: 7rpx;" :src="val.storeDto.storePic"></image>
				<text>{{val.storeDto.storeName}}</text>
			</view>
			<!-- 这一层是商品的遍历,item是商品信息,index是商品下标 -->
			<view v-for="(item,index) in val.shoppingTrolleyDtoList">
				<slideLeft @delItem="delItem(index,k)">
					<view class="cart-container">
						<view>
							<!-- 这一层是商品的勾选,我们可以传入商家和商品的下标,用于帮我们快速定位商品 -->
							<checkbox-group @change="checkboxChangeGood($event,item.id, k, index)">
								<checkbox class="round" style="transform:scale(0.7)" :checked="item.checked" />
							</checkbox-group>
						</view>
						<view style="background: #F2F2F2;">
							<image style="width:168rpx;height: 168rpx;" :src="item.goodsDto.goodsPicPathList[0]">
							</image>
						</view>
						<view class="message">
							<view style="font-size: 28rpx;">{{item.goodsDto.goodsName}}</view>
							<view class="guige">
								<view>{{message(index,k,item.goodsSpec)}}</view>
								<image style="width: 26rpx;height: 26rpx;" src="../../static/index/arrow.png"></image>
							</view>
							<view class="price">
								<view>{{item.goodsDto.price}}</view>
								<view>{{item.goodsPrice}}</view>
								<view class="amount">
									<view @click="sub(item,index,k)">-</view>
									<view>{{item.goodsCount}}</view>
									<view @click="add(item)">+</view>
								</view>
							</view>
						</view>
					</view>
				</slideLeft>
			</view>

		</view>

	</view>
</template>

<script>
	import slideLeft from '@/components/slide-left/slide-left.vue'
	import {
		cartList,
		deleteCart
	} from "../../src/api.js"
	export default {
		components: {
			slideLeft
		},
		data() {
			return {
				isAllChecked: false, //是否全选
				totalPrice: 0, //总价
				cartData: [], //数据
				// specifications: '',
				// color: ''
				// 首先,我们要实现的是全选,那么肯定会将所有的数据汇总到一个数组,那么建立totalArr用于存储
				// 此外,我们还要考虑存储的格式,每个商家代表一个对象,我们要的是商家里面的商品,所以:[[], []], [] 这样的格式
				// 之后,我们计算商品价格,那么就只需将数组扁平化,计算和即可。
				// 先考虑单个商品勾选时,怎么放入
				// 不过你现在这种方式也可以,直接在返回的数组上修改
				// 思路还是从单选商品开始
				totalArr: []
			}
		},
		onLoad() {
			this.list();
			
		},
		//监听页面滚动
		onPageScroll(res) {
			this.scrollTop = res.scrollTop
		},
		methods: {
			message(index, k, goodsSpec) {
				console.log(goodsSpec[1]);
				// console.log(goodsSpec[3]);
				let newGood1 = this.cartData[k].shoppingTrolleyDtoList[index].goodsSpecList[0].child;
				let newGood2 = this.cartData[k].shoppingTrolleyDtoList[index].goodsSpecList[1].child;
				console.log(newGood1.length);
				for (let i = 0; i < newGood1.length; i++) {
					if (goodsSpec[1] == i) {
						// break
						// console.log(newGood1[i].name);
						var specifications = newGood1[i].name;
					}
				}
				for (let j = 0; j < newGood2.length; j++) {
					if (goodsSpec[3] == j) {
						// break
						// console.log(newGood2[i].name);
						var color = newGood2[j].name;
					}
				}
				return [specifications, color];
			},
			async list() {
				let data = {
					userUuid: uni.getStorageSync('uuid'),
					currentPage: 1,
					pageSize: 10
				}
				let res = await cartList({
					header: {
						token: uni.getStorageSync('token'),
						clientType: 1
					},
					data: data
				})
				if (res.code == 200) {
					this.cartData = res.data;
					console.log(this.cartData)
				}
			},
			async delItem(index, k) {
				console.log(this.cartData[k].shoppingTrolleyDtoList[index].goodsUuid, "goodsUuid");
				console.log(this.cartData[k].shoppingTrolleyDtoList[index].storeUuid, "storeUuid");
				let data = {
					isSelect: 0,
					shoppingTrolleyVoList: [{
						storeUuid: this.cartData[k].shoppingTrolleyDtoList[index].storeUuid,
						goodsUuid: this.cartData[k].shoppingTrolleyDtoList[index].goodsUuid
					}]
				}
				let res = await deleteCart({
					method: "POST",
					header: {
						token: uni.getStorageSync('token'),
						clientType: 1
					},
					data: data
				})
				console.log(res.data)
				if (res.code == 200) {
					this.list();
					this.cartData = res.data;
				}
			},
			add(item) {
				item.goodsCount++;
			},
			sub(item, index, k) {
				console.log(this.cartList, "this.cartList");
				if (item.goodsCount <= 1) {
					uni.showModal({
						title: '提示',
						content: '确定删除吗',
						success: (res) => {
							if (res.confirm) {
								console.log('用户点击确定');
								console.log(index);
								this.delItem(index, k);

							} else if (res.cancel) {
								console.log('用户点击取消');
								item.count == 0;
							}
						}
					});
				} else {
					item.goodsCount--;
				}
			},
			// delItem(index) {
			// 	console.log(this.cartList, "this.cartList");
			// 	this.cartList.splice(index, 1)
			// 	console.log("删除了", index);
			// },
			submitOrder() { // 提交购物车订单
				uni.navigateTo({
					url: '/pages/address/address'
				})
			},
			
			

			// this.cartData.forEach(item => {
			// 	item.checked = this.checkedAll;
			// 	if (item.shoppingTrolleyDtoList) {
			// 		item.shoppingTrolleyDtoList.forEach(citem => {
			// 			citem.checked = this.checkedAll;
			// 		})
			// 	}
			// })
			
			// 合计
			setCart() {
				let totalPrice = 0;
				this.cartList.forEach(shop => {
					if (shop.checked) {
						shop['shoppingTrolleyDtoList'].forEach(good => {
							totalPrice += good.count * good.price
						})
					}
				})
				this.totalPrice = totalPrice
			},
			
			// 全选
			// 需要联动商家、商品
			checkboxChangeAll(e) {
				console.log(e,"全选的e");
				const isChecked = e.detail.value.length > 0;
				
				// 联动商家、商品
				this.cartData.forEach(shop => {
					console.log(shop['shoppingTrolleyDtoList'],"shop");
					this.$set(shop, 'checked', isChecked);
					shop['shoppingTrolleyDtoList'].forEach(good => {
						this.$set(good, 'checked', isChecked)
					})
				})
				
				console.log('购物车数据', this.cartData);
				console.log('全选数据', this.isAllChecked);
				
				// this.isAllChecked = !this.isAllChecked
				// this.cartList.forEach(v => v.isChecked = this.isAllChecked)
			},
			// 勾选商家
			// 需要联动更新商品、全选
			checkboxChangeShop(e, shopIndex) {
				console.log(e, shopIndex);
				const shop = this.cartData[shopIndex];
				// e.detail.length > 0 则是勾选中
				const isChecked = e.detail.value.length > 0;
				this.$set(shop, 'checked', isChecked); 
				
				// 联动商品
				const goods = this.cartData[shopIndex]['shoppingTrolleyDtoList']
				goods.forEach(g => {
					this.$set(g, 'checked', isChecked)
				});
				
				// 联动全选
				const shops = this.cartData
				if (isChecked) {
					let shopCheckSum = 0;
					// 判断是否所有商家都勾选
					shops.forEach(s => {
						if (s.checked) {
							shopCheckSum ++
						}
					})
					this.isAllChecked = shopCheckSum === shops.length;
				} else {
					this.isAllChecked = false
				}
				
				console.log('购物车数据', this.cartData);
				console.log('全选数据', this.isAllChecked);
				
			},
			// 勾选商品
			// 需要联动更新商家、全选
			checkboxChangeGood(e, id, shopIndex, goodIndex) {
				console.log(e, id, shopIndex, goodIndex);
				// 找到商品,并修改相关状态
				// e.detail.length > 0 则是勾选中
				const good = this.cartData[shopIndex]['shoppingTrolleyDtoList'][goodIndex]; // 单个商品
				this.$set(good, 'checked', e.detail.value.length > 0);
				
				// 商品状态变更,那么再考虑商家状态
				// 判断当前商家下面有多少个勾选的商品,如果勾选总数和商家拥有的商品总数一致,那么商家勾选中
				const goods = this.cartData[shopIndex]['shoppingTrolleyDtoList']; // 多个商品
				const shop = this.cartData[shopIndex]; // 单个商店
				let goodCheckSum = 0;
				goods.forEach(g => { 
					if (g.checked) {
						goodCheckSum ++;
					}
				})
				this.$set(shop, 'checked', goodCheckSum === goods.length);
			
				
				// 之后再判断全选
				// 当所有商家都勾选,那么全选就勾选上
				const shops = this.cartData; // 所有商店
				let shopCheckSum = 0;
				shops.forEach(s => {
					if (s.checked) {
						shopCheckSum ++
					}
				});
				this.isAllChecked = shopCheckSum === this.cartData.length;
				
				console.log('购物车数据', this.cartData);
				console.log('全选数据', this.isAllChecked);
				
				// console.log(e, id, this.cartData)
				// console.table({
				// 	'商家下标': shopIndex,
				// 	'商品下标': goodIndex
				// })
				// // var temp = []
				
			 
			// 	// 找到被修改的商品对象,this.cartData是最外层的,代表着商家,里面一层才是商品
			// 	let index = this.cartData.findIndex(v => v.id === id)
			// 	// 选中状态取反
			// 	this.cartData[index].isChecked = !this.cartData[index].isChecked
			
			// 	temp = this.cartData.every(v => v.isChecked)
			// 	if (temp) {
			// 		this.isAllChecked = true
			// 	} else {a
			// 		this.isAllChecked = false
			// 	}
			// 	this.setCart()
			},
		},
		

	}

</script>

<style lang="stylus" scoped>
	.cart-index{
	background-color: #F7F7F7;
	.cart-container{
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-top: 30rpx;
		margin-left: -20rpx;
		
	}
	.lement{
		position fixed;
		bottom 0;
		display flex;
		justify-content space-between;
		align-items center;
		padding 30rpx;
		font-size: 24rpx;
		background-color white;
		width: 93%;
		z-index:1000
		.now{
			background: #FF1E1E;
			border-radius 38rpx;
			font-size: 30rpx;
			color:white;
			padding 20rpx 65rpx
		}
	}
	.item{
		padding 0 30rpx 130rpx;
		background-color white;
		margin-top 15rpx
		.title{
			display flex;
			text{
				font-size 28rpx;
				margin-left 5px;
				margin-top 5rpx
			}
		}
		.message{
			width 61%;
			display flex;
			flex-direction column;
			justify-content space-between
			view:nth-child(1){
				// font-size: 28rpx;
			}
			.guige{
				display flex;
				justify-content space-around;
				align-items center;
				width 35%
				background: #F3F3F3;
				border-radius 20rpx;
				color: #999999;
				font-size 24rpx;
				padding 7rpx;
				margin-top 10rpx
			}
			.price{
				display flex;
				justify-content space-between;
				margin-top 10rpx
				view:nth-child(1){
					font-size 30rpx;					
					color: #FF1E1E;
				}
				view:nth-child(2){
					font-size: 24rpx;
					color: #999999;
					text-decoration: line-through;
					margin-top 5rpx;
					margin-left -60rpx
				}
			}
			.amount{
				display flex;
				view:nth-child(2){
					font-size: 28rpx;
					color: #333333;
					text-decoration: none;
					margin-left 20rpx;
					margin-right 20rpx
				}
				view:nth-child(1),view:nth-child(3){
					width:46rpx;
					height 46rpx;
					background-color #F4F4F4;
					text-align center;
					font-size 30rpx;
					color:black
				}
			}
		}
	}
}	
</style>

其中,slideLeft 组件是删除的效果
在这里插入图片描述
代码如下:

<template>
	<view>
		<view class="box-slideLeft" >
			<view class="touch-item touch-slideLeft " @touchstart="touchS" @touchmove="touchM" @touchend="touchE"  :style="item_show.txtStyle">
				<slot />
			</view>
 
			<view class="touch-item del-box-touch-slideLeft cf-shuCenter"  @click="delItem(item_show)">
				<view class="delete">删除</view>
			</view>
 
		</view>
	</view>
</template>
 
<script>
 
	export default {
		components: {
 
		},
		props: {
 
			data_transit: {
				type: Object,
				default () {
					return {}
				}
			},
			//可不传参
			item: {
				type: Object,
				default () {
					return {}
				}
			},
		},
		computed: {
 
		},
 
		data() {
			return {
 
				item_show : {},
				delBtnWidth: 60, //删除按钮宽度单位(rpx)
				startX: '',
			};
		},
		created:function(){
			//专门处理检查对象中,某字段是否存在的,如果存在返回 true 不存在返回 false
			let that = this ;
			let item = that.item ;
			if(!item.hasOwnProperty("txtStyle")){
				this.$set(this.item,'txtStyle','');//不需要初始化了
			}
			this.item_show = this.item ;
		},
		watch: {
			item(e){
				this.item_show = e ;
			},
		},
		methods: {
			//点击删除按钮事件
			delItem: function(e) {
				let that = this;
				let data ={
					item : e ,
					data : that.data_transit ,
				};
				console.log(data,"子组件的值");
				this.$emit('delItem', data);
			},
			touchS: function(e) {
				let that = this;
 
				if (e.touches.length == 1) {
					//设置触摸起始点水平方向位置
					this.startX = e.touches[0].clientX
 
				}
			},
			touchM: function(e) {
				let that = this;
 
				if (e.touches.length == 1) {
					//手指移动时水平方向位置
					var moveX = e.touches[0].clientX;
					//手指起始点位置与移动期间的差值
					var disX = this.startX - moveX;
					var delBtnWidth = this.delBtnWidth;
					var txtStyle = "";
					if (disX == 0 || disX < 0) { //如果移动距离小于等于0,说明向右滑动,文本层位置不变
						txtStyle = "left:0px";
					} else if (disX > 0) { //移动距离大于0,文本层left值等于手指移动距离
						txtStyle = "left:-" + disX + "px";
						if (disX >= delBtnWidth) {
							//控制手指移动距离最大值为删除按钮的宽度
							txtStyle = "left:-" + delBtnWidth + "px";
						}
					}
					//获取手指触摸的是哪一项
 
					that.item_show.txtStyle = txtStyle;
 
				}
			},
			touchE: function(e) {
				let that = this;
				if (e.changedTouches.length == 1) {
					//手指移动结束后水平位置
					var endX = e.changedTouches[0].clientX;
					//触摸开始与结束,手指移动的距离
					var disX = this.startX - endX;
					var delBtnWidth = this.delBtnWidth;
					//如果距离小于删除按钮的1/2,不显示删除按钮
					var txtStyle = disX > delBtnWidth / 2 ? "left:-" + delBtnWidth + "px" : "left:0px";
					//获取手指触摸的是哪一项
					that.item_show.txtStyle = txtStyle;
 
				}
			},
 
		}
 
	}
</script>
 
<style lang="scss">
	// @import './iconfont.css';//便于有删除图标
 
	.box-slideLeft {
		view {
			box-sizing: border-box;
		}
		position: relative;
		overflow: hidden;
 
		.touch-item {
			position: absolute;
			top: 0;
			padding: 10px 10px 10px;
			background-color: #FFFFFF;
			// border-radius: 10px;
			overflow: hidden;
		}
 
		.touch-slideLeft {
			position: relative;
			width: 100%;
			z-index: 5;
			transition: left 0.2s ease-in-out;
			// white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
		}
		.del-box-touch-slideLeft {
			right: 0;
			float: left;
			width: 70px;
			height: 100%;
			line-height: 101px;
			background-color: #FF1E1E;;
			border-radius: 0 10px 10px 0;
			color: #fff;
			font-size: 18px;
			font-weight: lighter;
			text-align: center;
		}
		.delete{
			font-size: 28rpx;
		}
		.icon-shanchu{
			font-size: 44upx;
		}
 
		.cf-shuCenter{
			display: flex;
			flex-direction: column;
			justify-content: center;
			align-items: center;
 
		}
	}
 
</style>
;