效果展示:
前言:
周六,我又如约而至的来带各位游玩云数据库了开发了。本篇文章重点讲解上篇文章(一周云开发,都来上“脚”国货之光小程序 )没有提到的购物车开发。若对鸿星尔克小程序的整体架构还没有了解的,可以通过传送门来到上篇文章了解一下。
传送门:(一周云开发,都来上“脚”国货之光小程序 )。
本次讲解的购物车开发,会遇到许多的坑,我会带着读者们随我开发的流程一一解决,话不多说直接开造。
开发流程:
设置shopcar数据库
首先我们得想一想要做出这个效果的购物车,我们购物车数据库得需要哪些字段属性? 从上图可以看出,首先需要图片pic,其次需要标题title,然后是价格price 和数量 number。 但购物车属性真只需要这些就可以了吗?下面就看看我构建的这张shopcar数据库表。
我在上文构思的基础上,增加了俩个属性,一个是datatime 操作更新的时间,来实现根据操作时间实现降序排序,一个是保存id值,能够实现点击购物车图片框传入id值,跳转详情页,详情页根据闯入的id值,在products数据库中取得id相同的数据。这里若有不懂的地方可以看上篇文章,详细介绍了如何实现。
大坑提示: 在创建完数据库后,一定要记得修改数据权限。不然这些操作shopcar数据库的操作,都无法在手机预览中实现,只能在电脑模拟器实现。这也是我第一篇文章录制数据库时无法对详情页加入购物车和操作购物车的原因。
利用@vant组件,构建购物导航栏,并添加addToShopcar事件
<van-goods-action>
<van-goods-action-icon class="icon" icon="chat-o" text="客服" dot />
<van-goods-action-icon class="icon" icon="shop-o" text="店铺" />
<van-goods-action-icon class="icon" icon="cart-o" text="购物车" info="5" bindtap="gotoShopcar" />
<van-goods-action-button class="button" text="立即购买" type="warning" />
<van-goods-action-button class="button" text="加入购物车" bindtap="addToShopcar" data-id="{{productinfo[0]._id}}"/>
</van-goods-action>
@vant 组件引入步骤
这里所有的icon都是@vant自带的标识。这样就利用@vant组件,帮助我来编写这个html+css。下面就由我简单来介绍下如何使用@vant。
1.在微信小程序详细,本地配设置里勾选使用npm模块。
2.在当前文件夹使用内建终端打开。
3.在内建终端中输入npm init -y 完成初始化
4. 完成初始化后输入 npm i @vant/weapp -S 下载@vant 包
5. 在微信小程序工具中构建npm
6. 查找@vant的使用文档:(文档链接 )查找需要的代码。
7. 在当前pages页面的json文件,或app.json引入所需组件。例如本次引入的购物栏使用的组件,我是在全局中引入。在app.json引用,组件能在小程序全局使用,若在pages页面下的json文件引入,只能在当前pages页面使用。
"usingComponents": { //app.json
"van-search": "./miniprogram_npm/@vant/weapp/search/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-grid": "@vant/weapp/grid/index",
"van-grid-item": "@vant/weapp/grid-item/index"
},
在addToShopcar事件中对shopcar数据库做添加操作
首先在添加addToShopcar中还传入了data-id="{{productinfo[0]._id}}"
当前详情页的_id。为什么要用_id呢?因为在云数据库中对数据库中做update操作时候,必须使用_id,这个唯一值才能进行。
async addToShopcar(e){
const id = e.currentTarget.dataset.id;
let{data:info} = await productsCollection
.where({
_id:id
})
.get();
let time = util.formatTime(new Date());
let {data:search} = await shopcarCollection.where({
id:info[0].id
})
.get();
if(!search.length){
shopcarCollection.add({
data: {
id:info[0].id,
title:info[0].title,
price: info[0].price,
number: this.data.number,
pic:info[0].pic,
datatime:time,
},
success: res => {
// 在返回结果中会包含新创建的记录的 _id
wx.showToast({
title: '新增记录成功',
})
console.log('[数据库] [新增记录] 成功,记录 _id: ', res._id)
},
fail: err => {
wx.showToast({
icon: 'none',
title: '新增记录失败'
})
console.error('[数据库] [新增记录] 失败:', err)
}
})
}
else{
let _id = search[0]._id;
let number = search[0].number;
shopcarCollection.doc(_id).update({
data:{
number:number+this.data.number,
datatime: time
},
})
}
},
首先将刚刚传入的_id值赋值给常量id。然后在数据库中查找并获取数据库中_id = id的值,并赋值给info。info数据就有我想放入shopcar数据库中大部分的数据。随后利用util包的工具调用方法,获得当前系统时间并赋值给time。
const id = e.currentTarget.dataset.id;//获取传入值
let{data:info} = await productsCollection
.where({
_id:id
})//利用传入_id,查找products中数据并获取
.get();
let time = util.formatTime(new Date());//得到当前操作时间
let {data:search} = await shopcarCollection.where({
id:info[0].id
})
.get();//查找是否在shocar数据库中出现该详情页数据
小坑提醒:想必在这里就有人可能想着直接将得到的info数据一股脑给到shopcar数据库中,完成新增购物车操作。但是我在添加购物车之前还进行了判断,因为如果shopcar购物车中已经有了准备添加的数据,不必添加其他信息,只需要update该数据项的number购买数量值和time操作时间值就可。
因此,我们先用id值对shopcar数据库进行查找 let {data:search} = await shopcarCollection.where({ id:info[0].id })
若找到,search.length=1,若没找到为0,可以变成我们执行不同操作的依据。
大坑提示:这里我们需要使用我们自己赋的值id,进行查找,而不能用_id查找。因为我们这里是通过products表对shopcar表进行操作,_id并不能将其关联,而我们自己设计的id将俩表数据关联起来了。因为在下面操作中将products对应的id传给了新增的shopcar的数据项的id。
若search.length=0,则是表示原来shopcar中查找中没有该详情页信息,则对shopcar数据库进行shopcarCollection.add操作,将products中的id,price,title,pic传给shopcar,还要将在详情页选购的数量num和当前的时间传给shopcar。
if(!search.length){//如果没出现
shopcarCollection.add({//添加数据
data: {
id:info[0].id,
title:info[0].title,
price: info[0].price,
number: this.data.number,
pic:info[0].pic,
datatime:time,
},
success: res => {
// 在返回结果中会包含新创建的记录的 _id
wx.showToast({
title: '新增记录成功',
})
console.log('[数据库] [新增记录] 成功,记录 _id: ', res._id)
},
fail: err => {
wx.showToast({
icon: 'none',
title: '新增记录失败'
})
console.error('[数据库] [新增记录] 失败:', err)
}
})
}
反之search.length=1,表示数据库中添加过该数据,则products中的信息已经添加过了,只需要对number购买数量和time更新时间进行修改。在利用共用的id,对shopcar数据库进行查找,查找到id属性为id值得数据,取出该数据的_id值,利用_id值对shopcar数据库进行update更新操作。在原来number数量的基础上,加上此次详情页的number,并重新更新time时间。
let _id = search[0]._id;//出现过,则update数据。
let number = search[0].number;
shopcarCollection.doc(_id).update({
data:{
number:number+this.data.number,
datatime: time
},
})
将数据库值赋给shopcar.js的data中,并利用wx:for显示。
async resetdate() {
let { data } = await shopcarCollection
.orderBy('datatime', 'desc')
.get()
let thingCarts = [];
data.forEach(item => {
let message = {
checked_son: false,
...item
}
thingCarts.push(message);
})
this.setData({
products: thingCarts,
})
},
对shopcar数据库先按时间进行降序排序获得并赋值给data。但是可能有一些人可能不懂,为什么我要声明一个thingCarts空数组呢?这就是我在开发中碰到的大坑。
大坑提醒: 因为购物车有勾选,来计算选中的总价格。那么如何判断选中呢?如果只在data数据中声明一个check,但是有很多数据,一个check属性根本无法进行判断。此时我第二个想法则是在shopcar数据库中声明一个check的bollean类型。但是因为数据库会保存,没法保证每次点开购物车时候,所有check值都是false值。但在一次我观看别人的购物车写法时候,突然豁然开阔。只需要将从shopcar数据库中的数据取得后,在每个数据的属性来添加一个check_son属性,初始化为false就能完美实现了。
因此只需要循环data数据,然后定义一个message对象,定义一个新的属性check_son:false,随后展开item,item便是一次循环data的一次数据。这里便是data数据,一次循环item便是一个索引开头的对象。 然后再将message对象push新的数组thingCarts,最后将thingCarts赋值给data中的products数据。下面是thingCarts的数据。这样购物车初始化默认不勾选就能完成了。 随后用wx:for 循环products就能实现购物车排列了。
计算勾选的总价格
定义total_price总价格
<van-submit-bar price="{{ total_price }}" button-text="提交订单"
bind:submit="onClickButton" tip="{{ true }}">
<van-tag type="primary">
<van-checkbox value="{{ checked_sub }}" checked-color="skyblue"
bind:change="onChangeOfSub">全选</van-checkbox>
</van-tag>
</van-submit-bar>
在shopcar.wxml中我又继续运用了@vant组件,这里显示总价格是固定的price=一个值,这个组件price中默认有俩位小数,并且得到的值会除以100,这里我们在js中的data中设立了一个total_price,这就是总价格。将price="{{ total_price }}"并且将total_price*100便能得到正常值。
定义onChange事件,在每次勾选时触发。
onChange(event) {
const priceall = this.data.total_price;
let {index} = event.currentTarget.dataset;
const change = "products[" + index + "].checked_son";
this.setData({
result: event.detail,
[change]: !this.data.products[index].checked_son
});
if(this.data.products[index].checked_son){
this.setData({
total_price:priceall+ this.data.products[index].price*this.data.products[index].number*100
})
}else{
this.setData({
total_price:priceall- this.data.products[index].price*this.data.products[index].number*100
})
}
},
思维梳理:勾选,就会在总价格中增加或减少当前勾选商品的价格*商品数量。那么怎么判断是增加或者减少呢?没错就是之前在products中添加的check_son,check_son默认为false,所以当勾选后,先将check_son取反,后对check_son进行判断,若为true则+,若为false则-。那么又一个问题来了,如何改变products数据中的存在的一个check_son属性呢?这便是我碰到的又一个大坑,在观看和学习他人代码时候我也终于得到了答案,使用字符串模板。
大坑提醒: 首先我们如何更改products中的一个属性呢?使用字符串模板,如何准确找到数组的第几项进行更改呢?传入索引Index值。因此我们得在设计这个事件的时候传入一个index值,这样就能实现了。我们要更改的值便是products.[index].checkson,因此定义变量const change = "products[" + index +"].checked_son";
随后直接在进行更改值 [change]值
this.setData({
result: event.detail,
[change]: !this.data.products[index].checked_son
});
这样便能更改products中的某些属性。
解决了这个问题,我们就能继续往后推了,直接判断this.data.products[index].checked_son
是否为true,为true,则取出products中number和price数据相乘再加到price_total中。
若为false则相减,这样便实现了单个勾选的实现。
定义onChangeOfHD事件,计算全选的总价格
onChangeOfHD(event) {
let index = 0;
let total =0;
this.setData({
checked: event.detail,
flag: !this.data.flag
});
this.data.products.forEach(element => {
const change = "products[" + index + "].checked_son";
this.setData({
[change]: !this.data.flag
})
if(this.data.products[index].checked_son){
total = total + this.data.products[index].price*this.data.products[index].number;
}
index = index+1
});
if(this.data.products[0].checked_son){
this.setData({
total_price:total*100
})
}else{
this.setData({
total_price:0*100
})
}
if (this.data.checked) {
this.setButton()
} else {
this.setData({
result: []
})
}
},
定义index=0,索引为0,定义total,计算总价格。并在page外的全局,在data中定义flag=false。这里的flag代表当前是否为全部全选,若为false则没有全选,若为true此时则是全选。每次来到这个事件时候,便要对flag进行取反。随后对products进行foreach循环,将每一个products中的每次循环的element改变check_son属性的值改为flag的值。这样就实现了按钮全选或全不选。`
let index = 0;
let total =0;
this.setData({
checked: event.detail,
flag: !this.data.flag
});
this.data.products.forEach(element => {
const change = "products[" + index + "].checked_son";
this.setData({
[change]: !this.data.flag
})
随后计算总价格,利用index的值,每次循环就能选中对应的products中数组定义的数据项。并且将其进行相加,得到了总价格total。随后判断products[0].check_son.若为true,则将total赋值给data中的total_price。若为false,则将total_price价格设为0。
总结:
本次购物车设计,是直接利用云数据来实现的,虽然比较有特色但并不是主流。一般实现的方法是利用微信小程序中自带的storage来实现的,详细的可以看:(# 快算算给你女朋友买礼物要花多少钱 🐱🏍! (仿得物微信小程序购物功能🤷♀️))。非常感谢在我写项目过程中帮助过我的老师和同学。如果你喜欢我的这篇文章或者看到这里对你有些许帮助,不妨点个赞吧。
下面是gitee源码:gitee.com/jhqaq/hon-x… 。不过需要自行添加粘贴app.json和project.config.json并替换appid为自己的微信小程序id。
app.json:
{
"pages": [
"pages/home/home",
"pages/test/test",
"pages/sort/sort",
"pages/home1/home1",
"pages/home2/home2",
"pages/shopcar/shopcar",
"pages/my/my",
"pages/search/search",
"pages/detail/detail",
"pages/detail2/detail2"
],
"window": {
"backgroundColor": "#F6F6F6",
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#F6F6F6",
"navigationBarTitleText": "鸿星尔克官方旗舰店",
"navigationBarTextStyle": "black"
},
"tabBar": {
"selectedColor": "#fece00",
"borderStyle": "white",
"backgroundColor": "#ededed",
"list": [
{
"text": "首页",
"pagePath": "pages/home/home",
"iconPath": "assets/images/tabs/home.png",
"selectedIconPath": "assets/images/tabs/home-active.png"
},
{
"text": "分类",
"pagePath": "pages/sort/sort",
"iconPath": "assets/images/tabs/sort.png",
"selectedIconPath": "assets/images/tabs/sort-active.png"
},
{
"text": "购物车",
"pagePath": "pages/shopcar/shopcar",
"iconPath": "assets/images/tabs/shopcar.png",
"selectedIconPath": "assets/images/tabs/shopcar-active.png"
},
{
"text": "我的",
"pagePath": "pages/my/my",
"iconPath": "assets/images/tabs/my.png",
"selectedIconPath": "assets/images/tabs/my-active.png"
}
]
},
"usingComponents": {
"van-search": "./miniprogram_npm/@vant/weapp/search/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-grid": "@vant/weapp/grid/index",
"van-grid-item": "@vant/weapp/grid-item/index"
},
"sitemapLocation": "sitemap.json",
"style": "v2"
}
复制代码
project.config.json
{
"miniprogramRoot": "miniprogram/",
"cloudfunctionRoot": "cloudfunctions/",
"setting": {
"urlCheck": true,
"es6": false,
"enhance": true,
"postcss": true,
"preloadBackgroundData": false,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": true,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"useIsolateContext": true,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"showES6CompileOption": false
},
"projectname": "weApp",
"libVersion": "2.14.1",
"cloudfunctionTemplateRoot": "cloudfunctionTemplate",
"appid": "wxb4a42d4b7e97a817", //这里需要更换成自己的id
"condition": {
"search": {
"list": []
},
"conversation": {
"list": []
},
"plugin": {
"list": []
},
"game": {
"list": []
},
"miniprogram": {
"list": [
{
"id": -1,
"name": "db guide",
"pathName": "pages/databaseGuide/databaseGuide"
}
]
}
}
}
复制代码
最后在云数据库中塞入一些数据就能看到效果了。