乐趣区

微信小程序之购物车和父子组件传值及calc的注意事项

在做微信小程序时,觉得小组里对购物车的实现不是很完美,就自己尝试的写了下,然后用到了父子组件传值,父子组件传值的话,和 vue 框架上是非常相似的,以及 calc 这个 css 函数,calc 有个注意点,自己不怎么用,一时间有差点忘了,这里记录下
1. 效果图

2. 子组件实现

要实现图中删除的效果,使用组件的形式更好做点,我当时本想直接在 pages 里实现,不过结果就是,滑动时,所有的商品都显示了删除按钮,除非用数组将每个商品要移动的距离存储起来,不过这样的话就很麻烦,所以我也是用组件来实现的
关于微信组件,可以直接点击链接访问官网查看自定义组件

子组件 index.wxml

<view class=”commodityItem” bindtouchstart=”handleTouchStart” bindtouchmove=”handleTouchMove” style=”transform:translateX({{-rightSpace}}px)”>
<view class=”selectedBtn” bindtap=”handleSelect” data-is-selected=”{{commodity.isselected}}”>
<view class=”noSelected” wx:if=”{{commodity.isselected==0}}”></view>
<image class=”selectedImg” wx:else src=”/images/selected.png”></image>
</view>
<view class=”commodityInfo”>
<view class=”commodityImg”>
<image src=”{{commodity.image}}”></image>
</view>
<view class=”commodityTitle”>
<view class=”title”>{{commodity.title}}</view>
<view class=”standard”> 规格:{{commodity.standard?commodity.standard:’ 无 ’}}</view>
<view class=”count”>
<view class=”price”>¥{{commodity.price}}</view>
<view class=”commodityNum”>
<i-input-number value=”{{selectedNum}}” min=”1″ max=”{{commodity.stock}}” bindchange=”numChange” />
</view>
</view>
</view>
</view>
<view class=”deleteBtn”>
<image class=”deleteImg” src=”/images/delete.png”></image>
<text class=”deleteText”> 删除 </text>
</view>
</view>
子组件 index.wxss
/* 商品 */
.commodityItem{
display: flex;
position: relative;
padding: 10rpx 24rpx 20rpx 30rpx;
box-sizing: border-box;
background: #fff;
transition: all .5s;
}
/* 选择按钮 */
.selectedBtn{
display: flex;
align-items: center;
width: 80rpx;
}
.noSelected{
width: 46rpx;
height: 46rpx;
border-radius: 50%;
border: 1px solid #ef5225;
}
.selectedBtn .selectedImg{
width: 50rpx;
height: 50rpx;
}
/* 商品信息 */
.commodityInfo{
display: flex;
width: calc(100% – 80rpx);
}
.commodityImg{
margin-right: 18rpx;
width: 220rpx;
height: 220rpx;
}
.commodityImg image{
width: 100%;
height: 100%;
vertical-align: middle;
}
/* 商品 title */
.commodityTitle{
width: calc(100% – 220rpx);
}
.title{
display: -webkit-box;
width: 100%;
height: 70rpx;
line-height:35rpx;
font-size: 24rpx;
font-weight:600;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.standard{
padding-top: 16rpx;
width: 100%;
height: 90rpx;
box-sizing: border-box;
}
.count{
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 60rpx;
}

/* 删除按钮 */
.deleteBtn{
display: flex;
position: absolute;
width: 70px;
height: 100%;
top: 0rpx;
right: -70px;
flex-direction: column;
align-items: center;
justify-content: center;
background: #ef5225;
}
.deleteImg{
margin-bottom: 10rpx;
width: 50rpx;
height: 50rpx;
vertical-align: middle;
}
.deleteText{
color: #fff;
}
子组件 index.json,这里用了 iview 中的数字输入框

{
“component”: true,
“usingComponents”: {
“i-input-number”: “/component/iview/input-number/index”
}
}
子组件 index.js
Component({

properties: {
commodity: Object,
},

data: {
touchStart: null,
rightSpace: 0,
selectedNum: 1,
},

methods: {
/* 商品是否选中 */
handleSelect() {
let selectedNum = this.data.selectedNum;
let commodity = this.data.commodity;
if(commodity.isselected == 0) {
commodity.isselected = 1;
} else {
commodity.isselected = 0;
}
this.triggerEvent(‘handleselect’, { commodity, selectedNum})
},
/* 处理触摸滑动开始 */
handleTouchStart(e) {
/* 记录触摸滑动初始位置 */
let touchStart = e.changedTouches[0].clientX;
this.setData({
touchStart
})
},
/* 处理触摸滑动 */
handleTouchMove(e) {
console.log(e)
let moveSpace = e.changedTouches[0].clientX;
let touchStart = this.data.touchStart;
if (touchStart != null) {
if (moveSpace – touchStart > 70) {
this.setData({
touchStart: null,
rightSpace: 0
})
}
else if (moveSpace – touchStart < -70) {
this.setData({
touchStart: null,
rightSpace: 70
})
}
}
},
numChange(e) {
let selectedNum = e.detail.value;
let commodity = this.data.commodity;
this.setData({
selectedNum
})
this.triggerEvent(‘handleselect’, { commodity, selectedNum})
}
}
})
3. 父组件实现
父组件 index.wxml,这里用的是假数据,所以操作上会有一些是联调时不必要的操作
<view class=”cart”>
<view class=”item” wx:for=”{{cartList}}” wx:key=”{{items.shopid}}” wx:for-item=”items”>
<view class=”storeInfo”>
<image class=”avatar” src=”{{items.logo}}”></image>
<view class=”storeName”>{{items.shopname}}</view>
</view>
<view class=”discount”> 满¥100 包邮,满 10 件包邮 </view>
<view class=”commodity” wx:for=”{{items.commodity}}” wx:key=”{{item.id}}”>
<cart-item commodity=”{{item}}” bind:handleselect=”handleSelect” />
</view>
</view>
<view class=”count”>
<view class=”selectAll” bindtap=”handleSelectAll”>
<view class=”noSelected” wx:if=”{{!isSelectedAll}}”></view>
<image class=”selectedImg” wx:else src=”/images/selected.png”></image>
<text class=”selectAllText”> 全选 </text>
</view>
<view class=”countPrice”>
<text> 合计:</text>
<text>¥{{countPrice}}</text>
</view>
<view class=”account”>
<text> 结算 </text>
<text>({{countSelectedNum}})</text>
</view>
</view>
</view>
父组件 index.wxss
page{
background: #f8f8f8;
}
.cart{
padding-bottom: 100rpx;
font-size: 26rpx;
}
.item{
border-bottom: 1px solid #eee;
}
/* 头部店铺信息 */
.storeInfo{
display: flex;
padding: 18rpx 0rpx 18rpx 30rpx;
background: #fff;
box-sizing: border-box;
}
.storeInfo .avatar{
width: 56rpx;
height: 56rpx;
border-radius: 50%;
vertical-align: middle;
}
.storeInfo .storeName{
margin-left: 16rpx;
line-height: 56rpx;
}
/* 包邮信息 */
.discount{
padding-left: 30rpx;
height:50rpx;
line-height: 50rpx;
font-size:20rpx;
color: #666;
box-sizing: border-box;
}
/* 底部操作 */
.count{
display: flex;
position: fixed;
padding-left: 30rpx;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
line-height: 100rpx;
box-sizing: border-box;
color: #232323;
background: #eee;
}
/* 全选 */
.selectAll{
display: flex;
padding-right: 20rpx;
align-items: center;
width: 25%;
font-size: 30rpx;
}
.selectAll .noSelected{
width: 46rpx;
height: 46rpx;
border-radius: 50%;
border: 1px solid #ef5225;
}
.selectAll .selectedImg{
width: 50rpx;
height: 50rpx;
}
.selectAllText{
margin-left: 18rpx;
}

.countPrice{
position: absolute;
top: 0;
right: 270rpx;
height: 100%;
line-height: 100rpx;
text-align: center;
font-size: 30rpx;
}
.countPrice text{
margin-right: 15rpx;
}
.account{
position: absolute;
top: 0;
right: 0;
width: 270rpx;
height: 100%;
line-height: 100rpx;
text-align: center;
font-size: 30rpx;
background: #ef5225;
color: #fff;
}
父组件 index.json,引用子组件
{
“usingComponents”: {
“cart-item”: “/component/cart/index”
}
}
父组件 index.js
Page({

data: {
cartList: [
{
shopname: ‘ 猫咪小店 ’,
logo: ‘/images/avatar.jpeg’,
shopid: 11,
commodity: [
{
id: 1,
image:’/images/commodity.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
standard: ‘111 + 黑色 ’,
price: ‘100’,
stock: 10,
quantity: 1,
isselected: 0,
},
{
id: 2,
image:’/images/avatar7.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
price: ’10’,
stock: 5,
quantity: 1,
isselected: 0,
}
]
},
{
shopname: ‘ 猫咪小店 ’,
logo: ‘/images/avatar5.jpg’,
shopid: 450,
commodity: [
{
id: 3,
image:’/images/commodity.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
price: ’90’,
stock: 10,
quantity: 1,
isselected: 0,
},
{
id: 4,
image:’/images/avatar7.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
price: ‘100’,
stock: 5,
quantity: 1,
isselected: 0,
},
{
id: 5,
image:’/images/commodity.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
standard: ‘111 + 黑色 ’,
price: ‘100’,
stock: 2,
quantity: 1,
isselected: 0,
}
]
},
{
shopname: ‘ 猫咪小店 ’,
logo: ‘/images/avatar.jpeg’,
shopid: 550,
commodity: [
{
id: 6,
image:’/images/avatar8.jpg’,
title: ‘ 雅诗兰黛鲜活焕亮红石榴晚霜 50ml 补水保湿 滋润排浊 ’,
standard: ‘111 + 黑色 ’,
price: ‘100’,
stock: 1,
quantity: 1,
isselected: 0,
}
]
},
],
/* 商品是否全选中 */
isSelectedAll: false,
/* 已选中商品的价格 */
countPrice: 0,
/* 统计所有选中的商品数量 */
countSelectedNum: 0,
},
/* 处理商品选中 */
handleSelect(e) {
let countPrice = 0;
let countSelectedNum = 0;
let cartList = this.data.cartList;
let length = cartList.length;

/* 因为是假数据,所以需要循环查找到对应的数据将其替换 */
for(let i = 0; i < length; i++) {
for(let j = 0; j < cartList[i].commodity.length; j++) {
if (cartList[i].commodity[j].id == e.detail.commodity.id) {
cartList[i].commodity[j] = e.detail.commodity;
cartList[i].commodity[j].selectedNum = e.detail.selectedNum;
}
if (cartList[i].commodity[j].isselected == 1) {
/* 点击选中的时候,计算价格,要判断下设置的商品选中数量,
* 我这里的是对点击了的商品才设置了选中的数量,所以需要对没有点击的商品数量设置为 1,然后就默认的加一
*/
if (cartList[i].commodity[j].selectedNum != undefined) {
countPrice += cartList[i].commodity[j].price * cartList[i].commodity[j].selectedNum;
countSelectedNum += cartList[i].commodity[j].selectedNum
} else {
countPrice += cartList[i].commodity[j].price * 1;
countSelectedNum += 1;
}
}
}
}

/* 对是否全选中进行判断 */
let isSelectedAll = true;
for (let i = 0; i < length; i++) {
for (let j = 0; j < cartList[i].commodity.length; j++) {
/* 若商品中的 isselecetd 有为 0 的就终止循环,直接设置为未全选 */
if (cartList[i].commodity[j].isselected == 0) {
isSelectedAll = false;
break;
}
}
}

this.setData({
cartList,
isSelectedAll,
countPrice,
countSelectedNum
})
},
/* 全选中商品 */
handleSelectAll() {
let isSelectedAll = !this.data.isSelectedAll;
let cartList = this.data.cartList;
let length = cartList.length;
let countPrice = 0;
let countSelectedNum = 0;

/* 遍历数据中的 isselected 来进行全选的操作 */
for(let i = 0; i < length; i++) {
for (let j = 0; j < cartList[i].commodity.length; j++) {
if(isSelectedAll) {
cartList[i].commodity[j].isselected = 1;
/* 全选的时候,计算价格,要判断下设置的商品选中数量,
* 我这里的是对点击了的商品才设置了选中的数量,所以需要对没有点击的商品数量设置为 1,然后就默认加一
*/
if (cartList[i].commodity[j].selectedNum != undefined) {
countPrice += parseInt(cartList[i].commodity[j].price) * cartList[i].commodity[j].selectedNum;
countSelectedNum += cartList[i].commodity[j].selectedNum;
} else {
countPrice += cartList[i].commodity[j].price * 1;
countSelectedNum += 1;
}
} else {
cartList[i].commodity[j].isselected = 0;
}
}
}

this.setData({
isSelectedAll,
cartList,
countPrice,
countSelectedNum
})
},
})
4. 父子组件传值

较常用的都是父组件往子组件传值,所以子组件往父组件传值就会不是很熟悉
我这里的话,是因为用的假数据,在点击商品选中或者不选中时,需要改变商品里的选中属性,所以用到了子组件往父组件传值,也包括传递选中的商品数量
子组件往父组件传值的话,是通过在调用 this.triggerEvent() 来实现的

/* 在父组件中定义方法:bind:handleselect 或者也可以直接写成 bindhandleselect*/
<cart-item commodity=”{{item}}” bind:handleselect=”handleSelect” />
在子组件中调用
this.triggerEvent(‘handleselect’, { commodity, selectedNum})
这个 this.triggerEvent(‘handleselect’, { commodity, selectedNum}) 方法中,handleselect 的名称要与父组件中引用子组件时绑定的方法名称一样,后面的对象就是传递的值,也可以直接是以直接量的形式传递,然后再父组件中通过 e.detail 来获取对应的值
handleSelect(e) {
console.log(e.detail)
console.log(e.detail.commodity)
console.log(e.detail.selectedNum)
}
5.calc 的注意事项
我以前也遇到过,然后现在再用的时候,一时间把这点给忘了,在看到编译器样式的时候,才猛然想起
.user-content{
padding: 10px 0 10px 50px;
width: calc(100% – 50px); /* 计算宽度,’+’ 或 ’-‘ 符号前后有空格 */
height: 18px;
}

css 中使用 calc 可以进行简单的运算:
单位可以是百分比,px,rem,em 等单位
使用 ”+”,”-“,”*”,”/” 运算符(使用 ”+” 或者 ”-“ 符号时,符号前后必须加上空格)
在 Firefox 浏览器上使用要加上 -moz 前缀
chrome 浏览器上使用要加上 -webkit 前缀
(使用 ”+” 或者 ”-“ 符号时,符号前后必须加上空格)

6. 部分想法

其实在样式上还是挺快就完成了,就是在计算商品价格的时候,想了挺久
在计算价格时,当时就有点蒙圈,总是想着要怎么判断他是增加数量还是减少数量,然后就陷入死循环的之中。
其实不用想她是增加还是减少数量,因为你都是传的是商品的数量,而且在计算时,也是判断了商品是否选中,所以,直接点,计算价格乘以数量就可以了
然后选中的商品数量的统计就和计算价格的思路是一样的了

正在努力学习中,若对你的学习有帮助,留下你的印记呗(点个赞咯 ^_^)

往期好文推荐:

判断 iOS 和 Android 及 PC 端
css 实现波浪线及立方体
微信小程序中遇到的多规格问题 (一)
实现单行及多行文字省略号

退出移动版