微信小程序云开发实战:购物车
- 前言
- 服务端
- 客户端
-
- 商品列表页面
- 购物车页面
- 下一节
前言
代码下载
一般购物车如上图所示。具体结算时,有满减、优惠卷、免单等各种方式。
服务端
在管理页面添加【活动】入口。
点击活动将会进入【活动】管理页面:
添加【活动】时,将从底部弹出actionsheet供选择活动类型:
在该页面的json文件中引入控件:
"mp-actionSheet": "/miniprogram_npm/weui-miniprogram/actionsheet/actionsheet",
<mp-actionSheet bindactiontap="btnClick" show="{
{showDialog}}" actions="{
{groups}}" title="请选择活动种类"></mp-actionSheet>
该groups变量的值:
groups: [{
text: '满减(一次)', value: 1 },{
text: '满减(叠加)', value: 2 },{
text: '免单', value: 3 },]
对于每一个【活动】,都可以选择其适用的商品,以及设定时间限制(下图为新建满减活动的页面):
客户端
购物车功能涉及客户端两个页面。对于原有的商品页面,需要增加【添加至购物车】的功能。而购物车页面属于新增加的页面。
商品列表页面
需要添加一个固定位置的按钮,当点击这个按钮时,会将选中的商品添加到购物车中。
这里有个问题,就是当切换商品类型时,用不用保存已选中商品的列表以便点击添加按钮后统一加入到购物车。目前demo是简单处理了,切换选项卡时清空保存选中商品的列表。
index.wxml:
<view wx:if="{
{pageIndex==0}}" class="add-wrap"><image src="../images/customer_user_add.png" bindtap="onAddToCard"></image></view>
cart-op云函数:
处理添加时要考虑重复添加商品的问题。我的做法是,对于重复添加的商品,将其在购物车中的数量递增,而且将其置于选中状态。
const user = await collection.where({
openid: wxContext.OPENID,cart: _.exists(true)}).get()//登陆的时候会创建空记录,此时只需要合并记录 var old_items = user.data.length > 0 ? user.data[0].cart : []console.log("old cart:", old_items)var add_items = []request.data.ids.forEach(id => {
var has = falsefor (var index =0; index < old_items.length; index++){
if( id == old_items[index].id){
//递增其数量old_items[index].count != null ? old_items[index].count +=1 : old_items[index].count = 1old_items[index].selected = truehas = true}}if (!has) {
add_items.push({
id: id,selected: true,count: 1})}})var new_items = old_itemsawait collection.where({
openid: wxContext.OPENID}).update({
data: {
cart: new_items}}).then(res => {
success = truedata = res.stats.updated}).catch(console.error)return {
success: false,}
index.js:
onAddToCard: function (e) {
let self = thisconsole.debug(this.data._selectedItems)wx.cloud.callFunction({
name: "cart-op",data: {
cmd: "add",data: {
ids: this.data._selectedItems}}}).then(res => {
console.debug(res)if (res.result.success) {
this.data._selectedItems = []for (var index = 0; index < self.data.goodsItems.length; index++) {
if (self.data.goodsItems[index].isChecked) {
self.setData({
//已经添加到购物车,取消这些商品的选中状态['goodsItems[' + index + '].isChecked']: false})}}if (res.result.data == 0) {
wx.showToast({
title: '已加入',})} else {
//如果有新加入的商品,在购物车图标上显示红点self.data.list[1].dot = trueself.setData({
['list[1]']: self.data.list[1]})}}}).catch(err => {
console.error(err)})},
购物车页面
该功能至少应该考虑下述因素:
- 已在购物车的再次添加如何处理
- 状态保存,包括商品是否选中、数量
- 商品删除时相应的处理
- 商品金额、总金额更新
cart-op云函数:
数据库的聚合查询:
购物车中只存放商品的id以及数量、是否选中等即时状态,并没有保存商品展示时需要的信息。如果对购物车中的每一种商品都依据id查找,将会非常耗时,能明显感觉到。如果将这些信息与购物车保存在一起,数据冗余是次要的,以后每次更新商品信息,都需要连带更新购物车,这是无法想象的。可以利用云数据库的聚合功能,将购物车中的cart列表字段展开,然后与goods数据表做连表查询(对于我这个多年来只会简单的select * form xxx的人来说,找这些资料并利用起来有些难度)。
case "get": {
const res = await collection.aggregate().unwind('$cart') //展开.lookup({
from: 'goods',localField: 'cart.id',foreignField: '_id',as: 'goodsInfo'}).project({
addressBook: 0,info: 0,orders: 0,_id: 0}).end()return {
success: true,data: res}}
购物车最终界面如下图所示:
我们需要在cart.json引入几个控件:
"usingComponents": {"van-stepper": "../../components/vant/dist/stepper/index","van-submit-bar" : "../../components/vant/dist/submit-bar/index","mp-cells": "/miniprogram_npm/weui-miniprogram/cells/cells","mp-cell": "/miniprogram_npm/weui-miniprogram/cell/cell","mp-slideview": "/miniprogram_npm/weui-miniprogram/slideview/slideview","mp-checkbox" : "/miniprogram_npm/weui-miniprogram/checkbox/checkbox", "mp-badge": "/miniprogram_npm/weui-miniprogram/badge/badge"}
submit-bar 挡住商品列表情况的处理:
这个透明的提交条不占据页面空间,所以滚动的时候它不会被作为可是元素处理,但会阻挡用户的输入信息。
就像上面的情况下,点击checkbox是无法触发事件的,作为一个在界面上不愿意多花时间成本的门外汉,处理方式就是塞给它一个没有内容的view元素,这样商品列表起码能滚动到submit-bar的上方:
关于活动的计价规则:
实际上的活动计价比demo中要复杂,比如满减这项,可区分整个购物车的商品总量满减还是单独的商品可单独计价,而且是否可与其他活动联合(活动是否具有排他性)。demo中是先将商品做一个单价由低到高的排序,然后遍历活动规则,每一次遍历都将参加活动的商品总额和数量统计出来,然后根据活动类型判断处理(比如,免单的时候,是先免掉单价低的商品)。
calculate: function (goodsList, discounts = []) {
var totalPrice = 0var selectedGoods = []goodsList.forEach(item => {
if (item.selected) {
item.order_price = Number((item.price * item.count).toFixed(2)) //单位为元totalPrice += item.order_price * 100 //单位为分selectedGoods.push(item)}})var sorted = selectedGoods.sort(function (a, b) {
return a.price - b.price})console.debug("sorted selected goods:", sorted)var cutPriceList = []for (var index = 0; index < discounts.length; index++) {
var discountGoods = []var totalCount = 0var total_Price = 0sorted.forEach(item => {
if (discounts[index].goods.indexOf(item._id) != -1) {
discountGoods.push(item)totalCount += item.counttotal_Price += item.order_price}})var cutPrice = 0;switch (discounts[index].type) {
case "1": {
//1 满N减Mif (total_Price >= discounts[index].total) {
cutPriceList.push({
title: discounts[index].title,cut: discounts[index].cut*100 //分})}}break;case "2": {
//2 每满N减Mvar number = Number.parseInt(total_Price / discounts[index].total)if(number > 0){
cutPriceList.push({
title : discounts[index].title,cut : discounts[index].title.cut*number*100 //分})}}break;case "3": {
//3 N免Mif (totalCount < discounts[index].total) {
break}var cutCount = discounts[index].cutvar start = 0while (cutCount > 0) {
var count = 1if (cutCount - discountGoods[start].count > 0) {
cutCount -= discountGoods[start].countcount = discountGoods[start].count} else {
count = cutCountcutCount = 0}cutPrice += Number((discountGoods[start].price * count).toFixed(2)) * 100start++}cutPriceList.push({
title : discounts[index].title,cut : cutPrice}) }break;}}var sortedCutPriceList = cutPriceList.sort(function (a, b) {
return a.cut - b.cut})sortedCutPriceList.forEach(item =>{
totalPrice -= item.cut})return totalPrice }
下一节
接下来我们将会实现订单功能