微信小程序开发之购物车总结
之前开发小程序也是一头雾水,也走了很多的坑今天写出来给大家分享一下开发过程中所走的弯路,加以总结,点滴积累,供大家参考,也希望大佬们指正。
说起购物车那也的有商品列表,列表中的有商品的种类,价格(及优惠价格),数量,之前的项目中的业务逻辑中带商品剩余的概念,所以在开发过程中的,用户在加减选择商品的时候,还得考虑商品的剩余数量,样式的问题一直让人很烦人,整理了一番也能看吧(老是感觉差点什么,得和前端小妹多聊会了哈哈),在代码中也会给大家分享我的实现方式。
开发的样式比较简陋的旨在说明问题,还请大佬轻喷,虚心讨教。
- 先说商品的列表展示,微信小程序中的也有组件scroll-view(可滚动的区域视图),具体的使用方法API比我说的清楚。
- 商品的展示一般是左边是商品的种类,右边是当前种类下的所有商品,数据库表的设计就不解释了相信大家都会
- 实现的就是左边商品种类和右边的商品详情的联动,右边商品的上下滑动也可是种类也随之选中
- 还有就是底部商品列表的弹出及商品的加减。
- 考虑组装什么样的数据(开发过程中使用的是一个map中套一个list)也就是map中的一个键值对就是一个商品种类及这个种类中的所有商品,最后统一放到list中转成json返回到前台。
顶端还有广告,这个暂时就不先写了(具体实现使用的是小程序的模板来渲染,在模板中也是来请求后台的广告列表)
在实现左右联动的时候遇到几个问题给大家分享下
- srcoll-view中的height属性必须要有不然视图不会滚动(而且在开发过程中注意高的使用,个人建议最好使用%这样可以兼容各种手机的高,之前的思路是获取当前用户的手机信息,来设置每个页面中的高【亲测还是%好用】)
- srcoll-view中的scroll-into-view属性要拿本本记下,因为开发过程中的id使用的是uuid也不能保证第一个开头的不是数字,所以在组装数据的时候要把id默认给设置不为数字开头
啰嗦了一顿那就开始我们的代码吧,首先我们先来上前端的js,wxml和wxss,上代码。
微信小程序中的js,啰嗦几句【也啰嗦不了几句】,方便自己记忆,也好自己复习一遍。
js中的默认的方法和Page的生命周期
- onLoad()。页面开始加载就进入这个方法【我一般初始获取数据和默认数据还有和data的交互】这个初始只加载一次
- onShow()。监听页面显示这个方法我的理解为刷新页面的数据使用
还是先来一个效果图:
上js代码:
先把数据整理一遍上js中组装的数据:
var list = {
"List":[{
"id": "40288ae767bee8f00167bf2c67f00017",
"className": "蔬菜",
"goods": [{
"standardPrice": 3.5,
"id": "40288ae868efbd940168f055ace70089",
"classId": "40288ae767bee8f00167bf2c67f00017",
"name": "白菜",
"image": "upload/cabbage.jpg",
"surplusNum": 13
},
{
"standardPrice": 4.0,
"id": "40288ae868efbd940168f055ace70000",
"classId": "40288ae767bee8f00167bf2c67f00017",
"name": "西红柿",
"image": "upload/tomato.jpg",
"surplusNum": 20,
"rebatePrice": 3.0
},
{
"standardPrice": 2.5,
"id": "40288ae868efbd940168f055ace71234",
"classId": "40288ae767bee8f00167bf2c67f00017",
"name": "菠菜",
"image": "upload/spinach.jpg",
"surplusNum": 20
}]
},
{
"id": "40288ae767bee8f00167bf2c67f00000",
"className": "水果",
"goods": [{
"standardPrice": 3.5,
"id": "40288ae868efbd940168f055ace70001",
"classId": "40288ae767bee8f00167bf2c67f00000",
"name": "苹果",
"image": "upload/apple.jpg",
"surplusNum": 13
},
{
"standardPrice": 4.0,
"id": "40288ae868efbd940168f055ace70002",
"classId": "40288ae767bee8f00167bf2c67f00000",
"name": "荔枝",
"image": "upload/litchi.jpg",
"surplusNum": 20,
"rebatePrice": 3.0
},
{
"standardPrice": 2.5,
"id": "40288ae868efbd940168f055ace70003",
"classId": "40288ae767bee8f00167bf2c67f00000",
"name": "香蕉",
"image": "upload/banana.jpg",
"surplusNum": 20
},
{
"standardPrice": 2.5,
"id": "40288ae868efbd940168f055ace70004",
"classId": "40288ae767bee8f00167bf2c67f00000",
"name": "葡萄",
"image": "upload/grape.jpg",
"surplusNum": 20
}]
},
{
"id": "40288ae767bee8f00167bf2c67f1111",
"className": "零食",
"goods": [{
"standardPrice": 16,
"id": "40288d3368efbd940168f055ace70006",
"classId": "40288ae767bee8f00167bf2c67f1111",
"name": "烟",
"image": "upload/apple.jpg",
"surplusNum": 15
},
{
"standardPrice": 400.0,
"id": "402834568efbd940168f055ace70007",
"classId": "40288ae767bee8f00167bf2c67f1111",
"name": "酒",
"image": "upload/litchi.jpg",
"surplusNum": 20
},
{
"standardPrice": 6,
"id": "40285ae868efbd940168f055ace70008",
"classId": "40288ae767bee8f00167bf2c67f1111",
"name": "糖",
"image": "upload/banana.jpg",
"surplusNum": 20,
"rebatePrice": 5.8
},
{
"standardPrice": 100,
"id": "40248ae868efbd940168f055ace70009",
"classId": "40288ae767bee8f00167bf2c67f1111",
"name": "茶",
"image": "upload/grape.jpg",
"surplusNum": 96
}]
}, {
"id": "12345e767bee8f00167bf2c67f00017",
"className": "蔬菜1",
"goods": [{
"standardPrice": 3.5,
"id": "40288ae868efb230168f055ace70089",
"classId": "12345e767bee8f00167bf2c67f00017",
"name": "白菜1",
"image": "upload/cabbage.jpg",
"surplusNum": 13
},
{
"standardPrice": 4.0,
"id": "40288ae868e56456468f055ace70000",
"classId": "12345e767bee8f00167bf2c67f00017",
"name": "西红柿1",
"image": "upload/tomato.jpg",
"surplusNum": 20,
"rebatePrice": 3.0
},
{
"standardPrice": 2.5,
"id": "40288ae86834ab940168f055ace71234",
"classId": "12345e767bee8f00167bf2c67f00017",
"name": "菠菜1",
"image": "upload/spinach.jpg",
"surplusNum": 20
}]
},
{
"id": "444444e767bee8f00167bf2c67f00000",
"className": "水果2",
"goods": [{
"standardPrice": 3.5,
"id": "40288ae868efbd940168f055ace70001",
"classId": "444444e767bee8f00167bf2c67f00000",
"name": "苹果2",
"image": "upload/apple.jpg",
"surplusNum": 13
},
{
"standardPrice": 4.0,
"id": "40288ae868efbd940168f055ace70002",
"classId": "444444e767bee8f00167bf2c67f00000",
"name": "荔枝2",
"image": "upload/litchi.jpg",
"surplusNum": 20,
"rebatePrice": 3.0
},
{
"standardPrice": 2.5,
"id": "4234234324a7b94b3028320489327",
"classId": "444444e767bee8f00167bf2c67f00000",
"name": "香蕉2",
"image": "upload/banana.jpg",
"surplusNum": 20
},
{
"standardPrice": 2.5,
"id": "40288ae86233268acf055ace70004",
"classId": "444444e767bee8f00167bf2c67f00000",
"name": "葡萄2",
"image": "upload/grape.jpg",
"surplusNum": 20
}]
},
{
"id": "40288ae767bee8f3333bf2c67f1111",
"className": "零食1",
"goods": [{
"standardPrice": 16,
"id": "4028809877efbd940168f055ace70006",
"classId": "40288ae767bee8f3333bf2c67f1111",
"name": "烟1",
"image": "upload/apple.jpg",
"surplusNum": 15
},
{
"standardPrice": 400.0,
"id": "4053458efbd940168f055ace70007",
"classId": "40288ae767bee8f3333bf2c67f1111",
"name": "酒1",
"image": "upload/litchi.jpg",
"surplusNum": 20
},
{
"standardPrice": 6,
"id": "40285a89786bd940168f055ace70008",
"classId": "40288ae767bee8f3333bf2c67f1111",
"name": "糖1",
"image": "upload/banana.jpg",
"surplusNum": 20,
"rebatePrice": 5.8
},
{
"standardPrice": 100,
"id": "4097876efbd940168f055ace70009",
"classId": "40288ae767bee8f3333bf2c67f1111",
"name": "茶1",
"image": "upload/grape.jpg",
"surplusNum": 96
}]
}]
}
module.exports = list;
js中有我自己使用的util包(先来两个)。
//判断字符是否为空的方法 const isEmpty = (obj) => { if (typeof obj == "undefined" || obj == null || obj == "") { return true; } else { return false; } } //提示信息 const showToast = (icon,title) => wx.showToast({ icon: isEmpty(icon) ? 'none' : icon, title: isEmpty(title) ? '提示信息' : title, mask: true })
上js代码:
var shopList = require('./shopList.js');
var app = getApp();
Page({
data: {
showCartDetail: false,
list: [],//商品列表
classifyViewed: '',//默认顶部
buyNum: {},
cart: [],
surplusNum: {},
sumMoney: 0.00, //购买总价
buySum:0//购买总数
},
/**
* 初始加载获取商品数据
*/
onLoad: function () {
//请求后台得来的商品list,在这里就不写怎么获取的
//在同级包下写了一个数据的js先引用讲解
var list = shopList.List;
// 购买商品的数量
var data = { buyNum: {} };
// 判断商品剩余使用,商品id为键 商品剩余为值
var surplusNum = {};
for (let i in list) {
var id = list[i].id;
// 更换商品种类(id不能以数字开头)
list[i].id = 'a' + id;
var goods = list[i].goods;
for (let j in goods) {
//拼接商品的图片,这里写死了开发过程中要写成配置文件
goods[j].image = 'http://127.0.0.1/object/' + goods[j].image;
data.buyNum[goods[j].id] = 0;
surplusNum[goods[j].id] = goods[j].surplusNum;//判断商品剩余
}
}
//原始list存入data中方便页面遍历
data.list = list;
this.setData(data);
this.setData({
surplusNum: surplusNum,
//作为一个比较的商品剩余
surplusnumInfo: surplusNum,
//默认一个商品种类选中
classifySeleted: list[0].id
});
},
//加
add: function (e) {
var detail = e.currentTarget.dataset.detail;//是否来自商品列表还是购物车
var productId, classGoodsIndex, goodsIndex, image, rebatePrice, price, productName;
if (detail == 'cart') {//购物车的加号
productId = e.currentTarget.dataset.id;
productName = e.currentTarget.dataset.name;
price = e.currentTarget.dataset.price;
} else {
productId = e.target.dataset.id;
classGoodsIndex = e.target.dataset.classgoodsindex;//商品种类的下标
goodsIndex = e.target.dataset.goodsindex;//商品的下标
image = this.data.list[classGoodsIndex].goods[goodsIndex].image;//商品图片
rebatePrice = this.data.list[classGoodsIndex].goods[goodsIndex].rebatePrice;//优惠价格
price = this.data.list[classGoodsIndex].goods[goodsIndex].standardPrice;//售价
productName = this.data.list[classGoodsIndex].goods[goodsIndex].name;//商品名字
}
//判断优惠价格,没有util包先简单判断一下
if (rebatePrice != null && rebatePrice != '') {
price = rebatePrice;
}
var arrId = [];//商品id集合
var arr = this.data.cart || [];
//判断商品剩余(第一次加入购物车肯定没有所以加一)
if (this.data.buyNum[productId] + 1 > this.data.surplusnumInfo[productId]) {
wx.showToast({
icon:'none',
title: '商品剩余数量不足',
})
return false;
}
var buyCount = 1;
//第一次加入商品
if (arr.length == 0) {
arr.push({
id: productId,
name: productName,
price: price,
buyCount: buyCount,
buyMoney: this.buyOneMoney(price, buyCount),
standardSellingPrice: this.data.list[classGoodsIndex].goods[goodsIndex].standardPrice,
image: image
});
} else if (arr.length >= 1) {
//商品id集合
for (let g in arr) {
arrId.push(arr[g].id);
}
//商品已存在的判断
if (arrId.indexOf(productId) != -1) {
for (let g in arr) {
if (arr[g].id == productId) {
arr[g].buyCount += 1;
buyCount = arr[g].buyCount;
arr[g].buyMoney = this.buyOneMoney(arr[g].price, arr[g].buyCount)
break;
}
}
} else {
arr.push({
id: productId,
name: productName,
price: price,
buyCount: buyCount,
buyMoney: this.buyOneMoney(price, buyCount),
image: image
});
}
}
var data = { buyNum: this.data.buyNum};
data.buyNum[productId] = buyCount;
//用来显示加减购物的数量显示
this.data.buyNum[productId] = buyCount;
this.setData(data);
//用于底部显示
this.setData({
sumMoney: this.buySumMoney(arr),
cart: arr,
buySum: this.buySum(arr),
});
//清空商品id集合
arrId = [];
//判断商品剩余与购买的数量
var surplusnum = this.data.surplusNum;
for (var i in surplusnum) {
if (productId == i && surplusnum[i] != 0) {
if (surplusnum[i] >= this.data.buyNum[i]) {
let count = surplusnum[i] - 1;
if (count <= 0) {
surplusnum[i] = 0;
break;
} else {
surplusnum[i] = count;
break;
}
} else {
break;
}
}
}
//更新商品剩余
this.setData({
surplusNum: surplusnum
})
},
//减
subtract: function (e) {
console.log('点击减号')
var productId = e.target.dataset.id
var arrId = [];
var buyCount = 0;
//判断商品剩余
// if (this.data.surplusNum[productId] >= this.data.surplusnumInfo[productId]) {
// return false;
// }
var arr = this.data.cart || [];
if (arr.length > 0) {
for (let i in arr) {
arrId.push(arr[i].id);
}
//商品在购物车中存在
if (arrId.indexOf(productId) != -1) {
for (let g in arr) {
if (arr[g].id == productId) {
arr[g].buyCount -= 1;
buyCount = arr[g].buyCount;
arr[g].buyMoney = this.buyOneMoney(arr[g].price, arr[g].buyCount)
}
if (arr[g].buyCount <= 0) {
this.removeByValue(arr, arr[g].id);
}
}
}
}
//判断购物车的商品
if (arr.length <= 0) {
this.setData({
cart: []
})
} else {
this.setData({
cart: arr
})
}
var data = { buyNum: this.data.buyNum };
data.buyNum[productId] = buyCount;
//用来显示加减购物的数量显示
this.data.buyNum[productId] = buyCount;
this.setData(data);
this.setData({
sumMoney: this.buySumMoney(arr),
cart: arr,
buySum: this.buySum(arr),
});
//清空商品id集合
arrId = [];
//判断剩余数量(剩余数量)
var surplusnum = this.data.surplusNum;
for (var i in surplusnum) {
if (productId == i && this.data.buyNum[i] >= 0) {
let count = surplusnum[i] + 1;
surplusnum[i] = count;
break;
}
}
//更新商品剩余
this.setData({
surplusNum: surplusnum
})
},
//该掏钱了,哈哈
submit: function () {
//组装下参数
var params = {};
},
//购买总数
buySum: function (array) {
var sum = 0;
for (var g in array) {
if (array[g].buyCount >= 0) {
sum += array[g].buyCount;
} else {
return sum;
}
}
return sum;
},
//单个商品总价
buyOneMoney: function (price, buyCount) {
if (buyCount <= 0) {
return 0.00;
} else {
return parseFloat(price * buyCount * 10000000 / 10000000).toFixed(2);
}
},
//购买总价
buySumMoney: function (array) {
var sum = 0.00;
for (let g in array) {
if (array[g].buyCount >= 0) {
sum += (array[g].buyCount * array[g].price * 10000000 / 10000000);
} else {
return sum;
}
}
return parseFloat(sum).toFixed(2);
},
//定义根据id删除数组的方法
removeByValue: function (array, val) {
for (let i = 0; i < array.length; i++) {
if (array[i].id == val) {
array.splice(i, 1);
break;
}
}
},
//左右关系联动
onGoodsScroll: function (e) {
if (e.detail.scrollTop > 10 && !this.data.scrollDown) {
this.setData({
scrollDown: true
});
} else if (e.detail.scrollTop < 10 && this.data.scrollDown) {
this.setData({
scrollDown: false
});
}
var scale = e.detail.scrollWidth / 570,
scrollTop = e.detail.scrollTop / scale,
h = 0,
classifySeleted,
len = this.data.list;
var list = this.data.list;
for (let i in list) {
//商品的高度也是根据总体的scrow-view来算的
var goodsHeight = 70 + list[i].goods.length * (46 * 3 + 20 * 2);
if (scrollTop >= h - 100 / scale) {
classifySeleted = list[i].id;
}
h += goodsHeight;
}
this.setData({
classifySeleted: classifySeleted
});
},
//左侧点击事件
tapClassify: function (e) {
var id = e.target.dataset.id;
this.setData({
classifyViewed: id
});
var self = this;
setTimeout(function () {
self.setData({
classifySeleted: id
});
}, 100);
},
//底部购物车显示
showCartDetail: function () {
this.setData({
showCartDetail: !this.data.showCartDetail
});
},
hideCartDetail: function () {
this.setData({
showCartDetail: false
});
}
});
上wxml代码:
<view class="content">
<!-- 左边商品种类 -->
<scroll-view class="classify-container" scroll-y="true">
<!-- classifySeleted指的是默认选中的那个种类样式就是给标亮 -->
<!-- bindtap="tapClassify"的意思滑动种类的区域要把顶部的这个种类给拿出来,方便右边的商品的联动 -->
<view class="classify {{classifySeleted==classGoods.id?'active':''}}" wx:for="{{list}}" wx:for-item="classGoods" wx:key="id" data-id="{{classGoods.id}}" bindtap="tapClassify">
<view class="name">{{classGoods.className}}</view>
</view>
</scroll-view>
<!-- 右边商品详情 -->
<scroll-view class="goods-container" scroll-y="true" scroll-into-view="{{classifyViewed}}" bindscroll="onGoodsScroll">
<view wx:for="{{list}}" wx:for-item="classGoods" wx:key="id" id="{{classGoods.id}}" wx:for-index="classGoodsIndex">
<view class="title">{{classGoods.className}}</view>
<!-- 遍历商品种类下的商品 -->
<view class="goods" wx:for-items="{{classGoods.goods}}" wx:for-item="item" wx:key="{{item.id}}" wx:for-index="goodsIndex">
<image class="image" src="{{item.image}}"></image>
<view class="name ellipsis">{{item.name}}</view>
<view class="surplus">剩余数量:{{surplusNum[item.id]}}</view>
<!-- 优惠价格的判断 -->
<view wx:if="{{item.rebatePrice == '' || item.rebatePrice == null}}">
<view class="price">¥{{item.standardPrice}}</view>
</view>
<view wx:else>
<view class="price">¥{{item.rebatePrice}}</view>
<view class='rebate-price' style='text-decoration:line-through;'>¥{{item.standardPrice}}</view>
</view>
<!-- 加减区域 -->
<view class='count-style'>
<!-- 加减采用的下标获取商品的信息 -->
<image src='../images/jian.png' data-id="{{item.id}}" data-classGoodsIndex="{{classGoodsIndex}}" data-goodsIndex="{{goodsIndex}}" bindtap='subtract'></image>
<text>{{buyNum[item.id]}}</text>
<image src='../images/jia.png' data-id="{{item.id}}" data-classGoodsIndex="{{classGoodsIndex}}" data-goodsIndex="{{goodsIndex}}" bindtap='add'></image>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="cart-detail" hidden="{{!showCartDetail || !buySum>0}}">
<view class="hidden-cart" bindtap="hideCartDetail"></view>
<scroll-view class="cart-list" scroll-y="true">
<view class="item" wx:for="{{cart}}" wx:for-index="id" wx:for-item="item" wx:key="id">
<view class="name ellipsis">{{item.name}}</view>
<view class="total">¥{{item.buyMoney}}</view>
<image class='cart-count-style' src='../images/jian.png' data-id="{{item.id}}" data-price="{{item.price}}" data-detail="detail" data-name="{{item.name}}" bindtap='subtract'></image>
<view class="num">{{item.buyCount}}</view>
<image class='cart-count-style' src='../images/jia.png' data-id="{{item.id}}" data-price="{{item.price}}" data-detail="cart" data-name="{{item.name}}" bindtap='add'></image>
</view>
</scroll-view>
</view>
<view class="cart">
<view class="showCartDetail" bindtap="showCartDetail">
<view class="sumMoney">¥{{sumMoney}}</view>
</view>
<button class='submit' bindtap='submit'>掏钱</button>
</view>
上wxss代码:
page {
height: 100%;
overflow: hidden;
}
.container {
height: 100%;
overflow: hidden;
}
.content {
display: -webkit-flex;
height: 100%;
width: 100%;
box-sizing: border-box;
padding-bottom: 100rpx;
}
.classify-container {
width: 180rpx;
background: #efefef;
height: 100%;
}
.classify-container .classify {
padding:30rpx;
text-align:center;
border-bottom:1px solid #f8f8f8;
}
.classify-container .classify.active {
background: #fff;
}
.classify-container .classify .name {
display: inline-block;
font-size: 30rpx;
color: #646464;
line-height: 1.2;
text-align: left;
pointer-events: none;
}
.goods-container {
background: #ffffff;
height: 100%;
}
.goods-container .title {
padding: 20rpx 25rpx;
color: #646464;
font-size: 30rpx;
line-height: 30rpx;
background: #f7f7f7;
}
.goods-container .goods {
position: relative;
padding: 20rpx 30rpx;
font-size: 36rpx;
line-height: 40rpx;
border-bottom: 1px solid #f7f7f7;
}
.goods-container .goods .image {
float: left;
width: 130rpx;
height: 130rpx;
margin-right: 20rpx;
}
.goods-container .goods .name {
color: #000;
font-size: 36rpx;
line-height: 46rpx;
}
.goods-container .goods .surplus {
color: #989898;
font-size: 25rpx;
line-height: 46rpx;
}
.goods-container .goods .price {
color: #f45044;
font-size: 30rpx;
line-height: 46rpx;
}
.goods-container .goods .addCart image {
pointer-events: none;
position: absolute;
left: 20rpx;
top: 20rpx;
width: 24rpx;
height: 24rpx;
}
.rebate-price{
color: #989898;
font-size: 24rpx;
line-height: 46rpx;
position:absolute;
right:260rpx;
top:115rpx;
text-decoration:line-through;
}
.cart-detail, .cart-detail .hidden-cart {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.cart-detail .cart-list {
position: absolute;
left: 0;
bottom: 100rpx;
width: 100%;
background: #f7f7f7;
padding: 30rpx 0;
max-height:800rpx;
}
.cart-detail .cart-list .item {
display: -webkit-flex;
color: #333;
font-size: 36rpx;
line-height: 50rpx;
padding: 20rpx 40rpx;
}
.cart-detail .cart-list .item .add {
font-size: 50rpx;
background: #feb70f;
width: 50rpx;
height: 50rpx;
text-align: center;
border-radius: 50%;
}
.cart-detail .cart-list .item .num {
width: 50rpx;
text-align: center;
margin: 0 5rpx;
}
.cart {
display: -webkit-flex;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 100rpx;
background: #f7f7f7;
}
.cart .showCartDetail {
-webkit-flex: 1;
/*border-top: 1rpx solid #e7e7e7;*/
}
.cart .showCartDetail{
position: absolute;
left: 15rpx;
top: 15rpx;
width: 70rpx;
height: 70rpx;
}
.cart .showCartDetail .sumMoney {
color: #f45044;
font-size: 36rpx;
line-height: 100rpx;
padding-left: 160rpx;
}
/*加减样式*/
.count-style{
display: flex;
width: 195rpx;
height: 40rpx;
position:absolute;
right:15rpx;
top:100rpx;
}
.count-style > image{
width: 44rpx;
height: 44rpx;
}
.count-style > text{
width: 105rpx;
height: 40rpx;
line-height: 40rpx;
font-size: 30rpx;
text-align: center;
color: #444444;
}
.cart-count-style{
width:45rpx;
height:45rpx;
}
.submit{
margin-right:20rpx;
width:200rpx;
}