慕尚花坊 --- 购物车
0. 准备工作
// 将原来的 Page 换位 Component
Component({
})
1. 页面构建
pages/cart/cart.json
{
"usingComponents": {
"van-card": "@vant/weapp/card/index",
"van-empty": "@vant/weapp/empty/index",
"van-button": "@vant/weapp/button/index",
"van-swipe-cell": "@vant/weapp/swipe-cell/index",
"van-checkbox": "@vant/weapp/checkbox/index",
"van-stepper": "@vant/weapp/stepper/index",
"van-submit-bar": "@vant/weapp/submit-bar/index"
}
}
pages/cart/cart.wxml
<view>
<!-- 购物车列表 -->
<view class="container" wx:if="{{ cartList.length !== 0 }}">
<view class="goods-item" wx:for="{{ cartList }}" wx:key="goodsId">
<van-swipe-cell class="goods-swipe" right-width="{{ 65 }}" async-close bind:close="onClose">
<van-cell-group>
<view class="goods-info">
<view class="left">
<van-checkbox value="{{ checked }}" checked-color="#FA4126" bind:change="onChange" />
</view>
<view class="mid">
<image class="img" src="{{ item.imageUrl }}" mode="" />
</view>
<view class="right">
<view class="title">{{ item.name }}</view>
<view class="right-bottom">
<view class="price">¥{{ item.price }}</view>
<!-- 点击的是加入购物车才显示步进器组件来修改商品数量 -->
<van-stepper class="stepper" button-size="22px" min="1" integer value="{{ item.count }}" bind:change="onChangeGoodsCount" />
</view>
</view>
</view>
</van-cell-group>
<view slot="right">删除</view>
</van-swipe-cell>
</view>
</view>
<!-- 购物车列表为空时, 展示的结构 -->
<van-empty wx:else description="{{ emptyDes }}">
<van-button type="danger" round>去购物</van-button>
</van-empty>
<van-submit-bar price="{{ 3050 }}" button-text="去结算" bind:submit="onClickButton" tip="{{ true }}">
<van-checkbox value="{{ selectAll }}" checked-color="#FA4126" bind:change="onChangeCheckBox"> 全选 </van-checkbox>
</van-submit-bar>
</view>
pages/cart/cart.scss
.container{
.goods-item {
background-color: rgb(240, 240, 240);
padding: 25rpx;
width: 690rpx;
margin-top: 20rpx;
.goods-swipe {
.goods-info {
display: flex;
height: 280rpx;
align-items: center;
.left {
van-checkbox {
line-height: 280rpx;
}
}
.mid {
.img {
width: 240rpx;
height: 280rpx;
}
}
.right {
width: 400rpx;
.title {
margin-left: 12rpx;
font-weight: 1000;
overflow: hidden; // 超出的文字以省略号代替
text-overflow: ellipsis;
white-space: nowrap;
}
.right-bottom {
display: flex;
.price {
margin-top: 160rpx;
margin-left: 12rpx;
color: orange;
font-size: 40rpx;
}
.stepper {
margin-top: 160rpx;
margin-left: 100rpx;
}
}
}
}
}
}
}
2. 判断用户是否登录
1. 购物车关联 Store 对象
/pages/cart/cart.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import { userStore } from '@/stores/userStore'
ComponentWithStore({
// 让页面和 Store 对象建立关联
storeBindings: {
store: userStore,
fields: ['token']
},
/**
* 页面的初始数据
*/
data: {
},
methods: {
}
})
2. 判断用户是否登录
- 如果没有登录, 购物车页面需要显示:
您尚未登录, 点击登录获取更多权益
pages/cart/cart.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
userStore
} from '@/stores/userStore'
ComponentWithStore({
// 让页面和 Store 对象建立关联
storeBindings: {
store: userStore,
fields: ['token'],
},
/**
* 页面的初始数据
*/
data: {
cartList: [],
emptyDes: '还没有添加商品, 快去添加吧~',
},
methods: {
// 展示文案信息, 同时获取购物车列表数据
showTipAndGetList() {
const {
token
} = this.data
// 如果用户没登录
if (!token) {
this.setData({
emptyDes: '您尚未登录, 点击登录获取更多权益',
cartList: []
})
return
}
},
onShow() {
this.showTipAndGetList()
}
}
})
3. 购物车列表
- 如果用户已经登录, 则获取购物车列表数据
- 如果购物车内没有商品, 显示:
还没有添加商品, 快去添加吧~ - 购物车列表有商品, 则使用数据对页面进行渲染
- 如果购物车内没有商品, 显示:
pages/cart/cart.wxml
<view class="container">
<!-- 购物车列表 -->
<view class="container" wx:if="{{ token && cartList.length !== 0 }}">
<view class="goods-item" wx:for="{{ cartList }}" wx:key="goodsId">
<van-swipe-cell class="goods-swipe" right-width="{{ 65 }}" async-close bind:close="onClose">
<van-cell-group>
<view class="goods-info">
<view class="left">
<van-checkbox value="{{ item.ischecked }}" checked-color="#FA4126" bind:change="onChange" />
</view>
<view class="mid">
<image class="img" src="{{ item.imageUrl }}" mode="" />
</view>
<view class="right">
<view class="title">{{ item.name }}</view>
<view class="right-bottom">
<view class="price">¥{{ item.price }}</view>
<!-- 点击的是加入购物车才显示步进器组件来修改商品数量 -->
<van-stepper class="stepper" button-size="22px" min="1" integer value="{{ item.count }}" bind:change="onChangeGoodsCount" />
</view>
</view>
</view>
</van-cell-group>
<view slot="right">删除</view>
</van-swipe-cell>
</view>
</view>
<!-- 购物车列表为空时, 展示的结构 -->
<van-empty wx:else description="{{ emptyDes }}">
<navigator url="/pages/index/index">
<van-button wx:if="{{ !token }}" type="danger" round>去登录</van-button>
</navigator>
<navigator url="/pages/index/index" open-type="switchTab">
<van-button wx:else type="danger" round>去购物</van-button>
</navigator>
</van-empty>
</view>
pages/cart/cart.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
userStore
} from '@/stores/userStore'
import {
reqCartList
} from '@/api/cart'
ComponentWithStore({
// 让页面和 Store 对象建立关联
storeBindings: {
store: userStore,
fields: ['token'],
},
/**
* 页面的初始数据
*/
data: {
cartList: [],
emptyDes: '还没有添加商品, 快去添加吧~',
},
methods: {
// 展示文案信息, 同时获取购物车列表数据
async showTipAndGetList() {
const {
token
} = this.data
if (!token) {
this.setData({
emptyDes: '您尚未登录, 点击登录获取更多权益',
cartList: []
})
return
}
// 如果用户登录了, 则需要请求购物车列表,
const {
code,
data: cartList
} = await reqCartList()
if (code === 200) {
this.setData({
cartList,
emptyDes: cartList.length === 0 ? '还没有添加商品, 快去添加吧~' : emptyDes
})
}
},
onShow() {
this.showTipAndGetList()
}
}
})
4. 更新商品的选择状态
点击商品的复选框时, 更新商品的购买状态
- 获取商品最新的购买状态, 将最新的状态同步到服务器(需要调用封装的接口 API 函数, 0: 不购买;1: 购买)
- 在服务器更新商品状态成功后, 将本地的数据改变
pages/cart/cart.wxml
<van-checkbox value="{{ item.isChecked }}" checked-color="#FA4126" bind:change="onChangeCheckBox" data-id="{{ item.goodsId }}" data-index="{{ index }}"/>
pages/cart/cart.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
userStore
} from '@/stores/userStore'
import {
reqCartList,
reqUpdateChecked
} from '@/api/cart'
ComponentWithStore({
data: {
cartList: [],
emptyDes: '还没有添加商品, 快去添加吧~',
},
methods: {
async onChangeCheckBox(event) {
// 获取用户的点击状态
const {
detail
} = event
// 获取携带的参数
const {
id,
index
} = event.currentTarget.dataset
const isChecked = detail ? 1 : 0
// 方法一: 发送请求, 更新商品的购买状态, 重新发送请求购物车列表页,并替换本地的数据
const res = await reqUpdateChecked(id, isChecked)
// if (res.code === 200){
// this.showTipGetList()
// }
// 方法二: 发送请求, 更新商品的购买状态,找到点击的商品索引, 更新对应索引数据的isChecked
if (res.code === 200) {
this.setData({
[`cartList[${index}].isChecked`]: isChecked
})
}
}
}
})
5. 计算是否全选 (计算属性)
基于购物车列表中已有的数据, 产生一个新的数据, 来控制全选按钮的选中效果, 就可以使用 计算属性 来实现
由于 ComponentWithComputed 方法 不能与 ComponentWithStore 方法 结合使用, 所以需要使用老版本的写法
pages/cart/cart.wxml
<van-submit-bar price="{{ 3050 }}" button-text="去结算" bind:submit="onClickButton" tip="{{ true }}">
<van-checkbox value="{{ selectAllStatus }}" checked-color="#FA4126"> 全选 </van-checkbox>
</van-submit-bar>
pages/cart/cart.js
// 1. 导入 computedBehavior
const computedBehavior = require('miniprogram-computed').behavior
ComponentWithStore({
// 2. 注册 computedBehavior
behaviors: [computedBehavior],
// 3. 定义计算属性, 计算属性会被挂载到 data 中
computed:{
// 判断是否是全选, 控制全选按钮的选中效果
selectAllStatus(data){
// computed 函数不能使用 this 来访问 data 中的数据, 需要使用形参 data
return data.cartList.length !== 0 && data.cartList.every(item => item.isChecked === 1)
}
},
})
6. 点击全选按钮
用户点击全选, 控制所有商品的选中和全不选效果
- 点击全选按钮, 获取全选按钮的选中状态
- 将状态同步给服务区 (1 是全选, 0 是全不选)
- 服务器更新成功后, 更新本地数据
pages/cart/cart.wxml
<van-submit-bar price="{{ 3050 }}" button-text="去结算" bind:submit="onClickButton" tip="{{ true }}">
<!-- 绑定 change 事件 -->
<van-checkbox value="{{ selectAllStatus }}" checked-color="#FA4126" bind:change="onChangeAllCheckBox"> 全选 </van-checkbox>
</van-submit-bar>
pages/cart/cart.js
import {
reqCheckAllStatus
} from '@/api/cart'
const computedBehavior = require('miniprogram-computed').behavior
ComponentWithStore({
methods: {
async onChangeAllCheckBox(event){
// 全选按钮的选中状态
const {detail} = event
const isChecked = detail ? 1 : 0
// 发送请求, 修改所有商品的选中状态, 并更新本地数据
const res = await reqCheckAllStatus(isChecked)
if (res.code === 200){
// 方式一: 发送请求, 更新商品的购买状态, 重新发送请求购物车列表页,并替换本地的数据
// this.showTipGetList()
// 方式二: 发送请求, 更新商品的购买状态,循环每个商品, 修改每个元素的 isChecked 属性
// 对购物车列表数据进行深拷贝
const newCartList = JSON.parse(JSON.stringify(this.data.cartList))
newCartList.forEach(item => item.isChecked = isChecked)
this.setData({
cartList: newCartList
})
}
}
}
})
7. 更新商品数量
当修改商品数量时, 不是直接将修改后的数量直接传给服务器, 而是计算差值 然后服务器进行算术运算
校验用户输入的商品数量是不是一个正整数
const reg = /^([1-9]|[1-9]\d|1\d{2}|200)$/
pages/cart/cart.html
<van-stepper class="stepper" button-size="22px" min="1" max="200" integer value="{{ item.count }}" bind:change="onChangeGoodsCount" data-id="{{ item.goodsId }}" data-index="{{ index }}" data-oldnum="{{ item.count }}"/>
pages/cart/cart.js
import {
reqAddToCart
} from '@/api/cart'
ComponentWithStore({
methods: {
async onChangeGoodsCount(event) {
// 用户最新输入的商品数量, 如果用户输入的购买数量大于200, 则需要把购买数量重置为200, 小于1,则重置为1
// 最大购买数量为200, 如果输入的是666, 重置为200, 差值为 200 - 1 = 199, 数据库为 199 + 1 = 200
const newNum = event.detail > 200 ? 200 : event.detail
// 获取携带的自定义数据
const {
id,
index,
oldnum
} = event.currentTarget.dataset
// 检验最新购买数量是否在 1 - 200 之间
const reg = /^([1-9]|[1-9]\d|1\d{2}|200)$/
const regRes = reg.test(newNum)
if (!regRes) {
// 如果验证未通过, 说明用户输入的数量不合法或者小于1, 需要还原为之前的购买数量, 并提示
this.setData({
[`cartList[${index}].count`]: oldnum
})
toast({
title: "商品数量输入不合法!"
})
return
}
// 如果验证通过, 计算出差值, 将差值发送给服务器
const disCount = newNum - oldnum
console.log(disCount);
// 判断是否发生改变, 如果没有发生改变, 不发送请求
if (disCount === 0) return
// 发送请求到服务器
const res = await reqAddToCart(id, disCount)
if (res.code === 200) {
// 方法一: 重新发送请求, 获取本地购物车列表
// this.showTipAndGetList()
// 方法二: 找到对应索引的商品,更新购买数量
this.setData({
[`cartList[${index}].count`]: newNum,
// 如果购买数量发生了变化, 需要将当前商品变成选中的状态
[`cartList[${index}].isChecked`]: 1
})
}
},
}
})
8. 更新商品数量防抖
1. 官方文档
https://licia.liriliri.io/zh/
2. npm下载
npm i miniprogram-licia
3. 防抖文档
https://licia.liriliri.io/zh/document.html#debounce
3. 使用
// 1. 导入
import {debounce} from 'miniprogram-licia'
ComponentWithStore({
methods: {
// 2. 改成普通函数, 并添加延迟时间
onChangeGoodsCount: debounce(async function (event) {
}, 500),
}
})
9. 总金额合计
- 判断哪些商品被勾选
- 将勾选的商品数量 * 商品单价, 然后进行累加
- 每次用户更新了商品的勾选状态 或 商品的购买数量, 都需要重新计算总金额
- 通过计算属性来实现
pages/cart/cart.html
<!-- 默认是以 分 为单位展示金额, 这里需要将总金额 * 100 -->
<van-submit-bar price="{{ totalPrice * 100 }}" button-text="去结算" bind:submit="onClickButton" tip="{{ true }}">
<van-checkbox value="{{ selectAllStatus }}" checked-color="#FA4126" bind:change="onChangeAllCheckBox"> 全选 </van-checkbox>
</van-submit-bar>
pages/cart/cart.js
ComponentWithStore({
computed: {
// 计算商品总金额
totalPrice(data){
let totalPrice = 0
data.cartList.forEach(item => {
// 判断商品是否是选中的状态
if (item.isChecked){
totalPrice += item.count * item.price
}
})
return totalPrice
}
},
})
10. 删除商品
pages/cart/cart.wxml
<view bind:tap="onSwipeCellPage">
<van-swipe-cell class="goods-swipe" id="swipe-cell-{{ item.goodsId }}" right-width="{{ 65 }}" bind:close="onClose" data-id="{{ item.goodsId }}" bind:open="swipeCellOpen" bind:tap="onSwipeCellClick">
<view slot="right" class="van-swipe-cell__right" bind:tap="delCartGoods">删除</view>
</van-swipe-cell>
</view>
pages/cart/cart.js
import {
reqDelCartGoods
} from '@/api/cart'
import {
swipeCellBehavior
} from '@/behaviors/swipeCell'
ComponentWithStore({
behaviors: [swipeCellBehavior],
methods: {
async delCartGoods(event){
const {id} = event.currentTarget.dataset
// 询问用户是否删除该商品
const res = await modal({
content: '您确认删除该商品吗?',
})
if (!res) return
const resReful = await reqDelCartGoods(id)
if (resReful.code === 200){
toast({
title: "删除成功"
})
// 重新请求购物车列表数据
this.showTipAndGetList()
}
},
onShow() {
this.showTipAndGetList()
},
// 关闭滑块
onHide(){
this.onSwipeCellCommonClick()
}
}
})
python防脱发技巧

浙公网安备 33010602011771号