文档章节

小程序仿饿了么弹窗式购物车

秀杰
 秀杰
发布于 2017/07/20 21:25
字数 1543
阅读 959
收藏 2

效果图

包含以下模块

  1. 选购页实现切换分类与数量加减
  2. 购物车动画与同步数量加减
  3. 购物车弹窗自适应商品条数

一、 点击分类项,切换右边的食品,并高亮自身

这个实现比较简单,给一个states数组,用于记录每一项分类的状态,点击设为true,wxml渲染时三目运算判断即可。

categoryStates = categoryStates.map(function (item, i) {
	if (index == i) {
		item = true;
	} else {
		item = false;
	}
	return item;
});

相应的wxml文件,class="{{categoryStates[index] ? 'category-item-active' : ''}}"

二、 加减按钮

  1. 初始只有一个加号
  2. 点击加号后,相应商品数量+1,并出现减号
  3. 减至0时,减号消失,连同数量值

设计数组结构

cartData: {},它的键是Food表的objectId,值是数量。

以下是js代码实现

add: function (e) {
	// 所点商品id
	var foodId = e.currentTarget.dataset.foodId;
	console.log(foodId);
	// 读取目前购物车数据
	var cartData = that.data.cartData;
	// 获取当前商品数量
	var foodCount = cartData[foodId] ? cartData[foodId] : 0;
	// 自增1后存回
	cartData[foodId] = ++foodCount;
	// 设值到data数据中
	that.setData({
		cartData: cartData
	});
}

在wxml文件中绑定数据如下

<view class="stepper">
	<!-- 减号 -->
	<view class="symbol subtract" wx:if="{{cartData[item.objectId]}}">-</view>
	<!-- 数量 -->
	<view class="value">{{cartData[item.objectId]}}</view>
	<!-- 加号 -->
	<view class="symbol add" bindtap="add" data-food-id="{{item.objectId}}">+</view>
</view>

上述代码中,通过wx:if判断当前商品的数量是否存在,无则不显示减号按钮;而在加号按钮旁要显示的数量就是{{cartData[item.objectId]}};点击事件传递的foodId就是{{item.objectId}}

减法按钮类似

subtract: function (e) {
	// 所点商品id
	var foodId = e.currentTarget.dataset.foodId;
	// 读取目前购物车数据
	var cartData = that.data.cartData;
	// 获取当前商品数量
	var foodCount = cartData[foodId];
	// 自减1
	--foodCount;
	// 减到零了就直接移除
	if (foodCount == 0) {
		delete cartData[foodId]
	} else {
		cartData[foodId] = foodCount;
	}
	// 设值到data数据中
	that.setData({
		cartData: cartData
	});
}

三、购物车动画

cascadeToggle: function () {
        //切换购物车开与关
	if (that.data.maskVisual == 'show') {
		that.cascadeDismiss();
	} else {
		that.cascadePopup();
	}
},
cascadePopup: function () {
	// 购物车打开动画
	var animation = wx.createAnimation({
		duration: 500,
		timingFunction: 'ease-in-out',
	});
	this.animation = animation;
	animation.translateY(-285).step();
	this.setData({
		animationData: this.animation.export(),
		maskVisual: 'show'
	});
},
cascadeDismiss: function () {
        // 购物车关闭动画
	this.animation.translateY(285).step();
	this.setData({
		animationData: this.animation.export(),
		maskVisual: 'hidden'
	});
}

通过点击控制显示与隐藏,<view class="ft" bindtap="cascadeToggle">

而<view>层级通过z-index来解决,其中底部购物车.ft区别权重最高,设为999,其次是弹窗主体.modal-content,其余默认不设定。

四、购物车加减

首先要读取购物车数据,即cartData,它是以foodId为key,数量为value的object,所以需要转换为array,才能很好地被遍历。

cartToArray: function (foodId) {
	// 需要判断购物车数据中是否已经包含了原商品,从而决定新添加还是仅修改它的数量
	var cartData = that.data.cartData;
	var cartObjects = that.data.cartObjects;
    var query = new Bmob.Query('Food');
    // 查询对象
    query.get(foodId).then(function (food) {
    	// 从数组找到该商品,并修改它的数量
    	for (var i = 0; i < cartObjects.length; i++) {
    		if (cartObjects[i].food.id == foodId) {
    			// 如果是undefined,那么就是通过点减号被删完了
    			if (cartData[foodId] == undefined) {
    				delete cartObjects[i];
    			} else {
        			cartObjects[i].quantity = cartData[foodId];
    			}
    			that.setData({
    				cartObjects: cartObjects
    			});
    			// 成功找到直接返回,不再执行添加
    			return ;
    		}
    	}
    	// 添加商品到数组
        var cart = {};
        cart.food = food;
        cart.quantity = cartData[foodId];
    	cartObjects.push(cart);
    	that.setData({
    		cartObjects: cartObjects
    	});
    });
}

然后在add/subtract方法末尾中调用它就可以购物车键值对转换成对象数组。

那接下来就顺理成章了,直接购物车小弹窗里将cartObjects渲染就可以了。

<view class="modal-body">
	<view class="item" wx:for="{{cartObjects}}">
		<view class="title">{{item.food.title}}</view>
		<view class="fee">{{item.food.price * item.quantity}}</view>
		<view class="stepper">
			<!-- 减号 -->
			<view class="symbol subtract" bindtap="subtract" wx:if="{{cartData[item.food.objectId]}}" data-food-id="{{item.food.objectId}}">-</view>
			<!-- 数量 -->
			<view class="value">{{cartData[item.food.objectId]}}</view>
			<!-- 加号 -->
			<view class="symbol add" bindtap="add" data-food-id="{{item.food.objectId}}">+</view>
		</view>
	</view>
</view>

得益于MVVM数据绑定,因此在购物车里点加减也实时地同步了商品列表中显示的数量

汇总

amount: function() {
	var cartObjects = that.data.cartObjects;
	var amount = 0;
	cartObjects.forEach(function (item, index) {
		amount += item.quantity * item.food.get('price');
	});
	that.setData({
		amount: amount
	});
}

这里多请求了一次网络,由于请求是异步的,所以我将汇总代码丢在网络请求里,于cartToArray方法内。

减法与加法基本类似,值得一提的是,减法要判断非负的合法性,所以将自减至零时,直接将元素通过delete操作移除,省去后续提交购物车遍历汇总的非零判断的烦琐。

五、购物车高度自适应

购物车列表用的是view作为容器,这样列表是不能在弹窗里滚动的,因此要将购物车弹窗改为scroll-view显示,并且高度自适应行数,直至一个max-height高度

改造

<view class="modal-body">替换成<scroll-view class="modal-body" scroll-y="true">,同时在样式表中为它增加一个高度height值。

bug

过程出现了一个bug,外部的商品列表上拉时再触发购物车弹窗会出现有白屏区块。

在wxml面板查看,发现是modal-mask层随着页面滚动而上移了

果断修改样式.modal-mask {position: absolute;}.modal-mask {position: fixed;}

问题迎刃而解。

进一步改造

将高亮动态化,由wxss样式转移到.js代码中控制

1、先注释掉wxss中硬编码的height样式

/*弹窗主体*/
.modal-content {
	position: fixed;
	bottom: -235px;
	left: 0;
	width: 100%;
	/*height: 235px;*/
	margin-top: 5px;
	background: #fff;
	z-index: 99;
}
/*内容区域*/
.modal-body {
	font-size: 14px;
	/*height: 145px;*/
	max-height: 295px;
}

2、在js文件声明成员变量

// 最大行数
var max_row_height = 5;
// 行高
var cart_footer_offset = 90;
// 底部栏偏移量
var food_row_height = 49;

设定最大行数,是因为这个弹窗不能无限伸展,否则都盖过整个屏幕了;在最大行数以内,都应该自动适应高度。

3、计算高度值

// scrollHeight为商品列表本身的高度
var scrollHeight = (that.data.cartObjects.length <= max_row_height ? that.data.cartObjects.length : max_row_height) * food_row_height;
// cartHeight为整个购物车的高度,也就是包含了标题栏与底部栏的高度
var cartHeight = scrollHeight + cart_offset;
animation.translateY(- cartHeight).step();
that.setData({
	scrollHeight: scrollHeight,
	cartHeight: cartHeight
});

4、wxml绑定数据

<view animation="{{animationData}}" class="modal-content" style="height: {{cartHeight}}px; bottom: -{{cartHeight}}px;">
...
        <scroll-view class="modal-body" scroll-y="true" style="height: {{scrollHeight}}px;">

到此不论购物车内有几条数据,都能很好的自适应;超过5条,开始滚动视图,大功告成

源码下载:http://git.oschina.net/laeser/dinner,或者关注公众号【黄秀杰】,回复112。

© 著作权归作者所有

秀杰
粉丝 151
博文 94
码字总数 50956
作品 0
瑞安
iOS工程师
私信 提问
加载中

评论(2)

皮皮虾仁
找错人了,错怪博主了,评论不知道怎么删除,只好做个补充留言,向博主道歉。
皮皮虾仁
黄秀杰,领了老子红包就拉黑我,有出息了哈?再打赏你点?啧啧,长这个样子啊!快,谢谢我,给你评论了这么多
自定义控件及效果

Android 动画效果定值范围选择控件 实现固定值的范围选择, 并添加动态效果, 使用方便 项目需求讨论 - Android 自定义 Dialog 实现步骤及封装 根据实际项目需求出发。因为项目中的对话框要配合...

掘金官方
2018/01/09
0
0
前端基础知识

Understanding ECMAScript 6 中文版(可下载电子书) Nicholas C. Zakas 大神的新著作,2016 年 8 月 30 日出版。 在 GitBook 页面上阅读本书全文,也可以下载 PDF 、 Mobi 或 ePub 格式的电...

掘金官方
2017/12/12
0
0
android仿摩拜单车APP、炫酷RecyclerView、卡片滑动、仿饿了么点餐、自定义索引等源码

Android精选源码 Android自定义索引源码(http://www.apkbus.com/thread-599163-1-1.html) LayoutManager实现的卡片滑动(http://www.apkbus.com/thread-599396-1-1.html) android仿摩拜单车A......

逆鳞龙
2018/05/22
0
0
HTML5手机端弹窗、提示框、loading加载(多功能xwPop弹窗升级版)

手机移动端响应式动画弹窗提示框插件xwPop,原生JS实现,带CSS3动画效果,丰富的JS弹出框提示信息插件,支持基本信息提示、带图标loading信息提示、confirm提示、自定义信息提示图标,仿Ios...

xiaoyan2015
2018/01/11
0
0
可能是github上第一款Vue全家桶+Typescript的完整项目

vue-ts-daily 基于Vue.js的2.5.13版本和TypeScript编写的模仿原生应用的WebApp. 👉项目演示地址欢迎star✨ ps: 服务器不在内地,加载可能慢点... 建议直接添加到主屏幕(ios端体验差一些). ...

寻找海蓝96
2018/05/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Angular 英雄编辑器

应用程序现在有了基本的标题。 接下来你要创建一个新的组件来显示英雄信息并且把这个组件放到应用程序的外壳里去。 创建英雄组件 使用 Angular CLI 创建一个名为 heroes 的新组件。 ng gener...

honeymoose
43分钟前
4
0
Kernel DMA

为什么会有DMA(直接内存访问)?我们知道通常情况下,内存数据跟外设之间的通信是通过cpu来传递的。cpu运行io指令将数据从内存拷贝到外设的io端口,或者从外设的io端口拷贝到内存。由于外设...

yepanl
今天
6
0
hive

一、hive的定义: Hive是一个SQL解析引擎,将SQL语句转译成MR Job,然后再在Hadoop平台上运行,达到快速开发的目的 Hive中的表是纯逻辑表,就只是表的定义,即表的元数据。本质就是Hadoop的目...

霉男纸
今天
3
0
二、Spring Cloud—Eureka(Greenwich.SR1)

注:本系列文章所用工具及版本如下:开发工具(IDEA 2018.3.5),Spring Boot(2.1.3.RELEASE),Spring Cloud(Greenwich.SR1),Maven(3.6.0),JDK(1.8) Eureka: Eureka是Netflix开发...

倪伟伟
昨天
13
0
eclipse常用插件

amaterasUML https://takezoe.github.io/amateras-update-site/ https://github.com/takezoe/amateras-modeler modelGoon https://www.cnblogs.com/aademeng/articles/6890266.html......

大头鬼_yc
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部