前言
微信小程序简单易上手,只要有一些编程基础,即可快速开发基本的项目。
本项目是常见的,商品广告落地页小程序。提供商品浏览,商品列表按钮,购买,微信授权,手机号绑定,验证码校验,用户协议,消息通知,监听者模式等基本功能。
定位学习人群是刚接触微信小程序的,零基础同学。
本人刚接触微信小程序时,也是零基础,HTML和CSS都是第一次接触,经过两个星期的学习,就掌握了基本的开发技巧,并独立完成多个项目。所以即使没有这方面经验的同学也不要气馁,只要学习几个实战项目之后,应付工作基本上都是绰绰有余的。
效果图展示:
目录
创建小程序
落地页可适配长度界面
落地页底部栏:
客服按钮,购买按钮微信登录授权底部用户协议,用户协议同意定位
3 x 3 按钮组件
组件使用 flex-growflex-shrink 组件传递数据CSS关键帧动画
拼团成功组件
广告轮播
水平广告轮播消息轮播
下浮层
Notification 监听者模式
Toast 提示弹窗
总结
代码下载
创建
创建一个小程序项目,如果只是学习,那只需要下载安装 微信开发者工具。如果是商用的话,需要申请APPID,并根据自己需要开通相应的功能,例如支付接口,以及申请自己的资源CDN。
下面介绍如何创建一个小程序:
启动工具之后,点击“+” 创建小程序。
如果没有申请APPID,可以使用测试号,就是随机生成的测试号,只是本地开发使用,不可以商用。
选择不使用云服务,点击创建,选择编程语言。然后点击确定。
这样就来到项目界面。
— NEXT —
落地页可适配长度界面
本小节,我们来实现一下落地页可适配长度的滚动界面。
需要创建page,名称就叫做landingpage。
在app.json中,添加启动页,输入名称,按下回车,会自动在pages/路径下生成文件夹,并生成landingpage.js,landingpage.json,landingpage.wxml,landingpage.wxss四个文件。
{"pages":["pages/landingpage/landingpage",...]}
我习惯先写.wxml文件,然后在.wxss文件中随时调试界面样式,涉及到引用的组件,在.json文件中添加即可。界面逻辑写在.js文件中。
分析界面结构:
整体结构为纵向垂直布局,可以先设置几张图片依次平铺。先设置landingpage整体样式:
<view class='main-wrap'></view>
.main-wrap {position: relative;display: flex;flex-direction: column;background: #EEE;}
使用wx:for设置一组图片,wx:key可以写成*this
<block wx:for="{{bannerImgList}}" wx:for-index="index" wx:key="*this"><image class="banner" mode="widthFix" src="{{item}}" lazy-load="true" /></block>
图片样式为:
.banner {width: 100%;height: auto;}
这里bannerImgList为本地一组图片资源,在data中声明:
bannerImgList: ['../../images/landingpage1.jpg','../../images/landingpage6.jpg','../../images/landingpage7.jpg','../../images/landingpage8.jpg',]
这样,落地页基本就有了一个简单的界面,图片从上到下铺满屏幕。
注意这里的图片在实际项目中,需要使用CDN的下载地址,不然本地资源太多,影响小程序加载速度,而且上传小程序也有尺寸限制。
Tips:
书写wxml标签快捷方式: 输入view.className回车,会自动生成<view class=“className”></view>输入view#idName回车,会自动生成 *<view id=“idName”></view>,其他标签同理。微信小程序,自定义组件不支持id选择器,所以注意在组件中要使用类选择器。图片加载方式设置为lazy-load表示需要显示图片时才显示,这样做能提高界面刷新效率。— NEXT —
落地页底部栏:
本小节,我们布局底部栏,包含用户协议,和两个按钮。
分析界面布局
上下两层结构,内部为水平布局。先创建一个bottom容器:
<view class="bottom-box"></view>
.bottom-box {position: relative;width: 100%;height: 120rpx;}
再添加两个按钮和文字:
<view id="bottom-wrap" style="padding-bottom:{{safeAreaHeight}}rpx;"><view id="kefu" bindtap="tapKefu" hover-class="button-hover"><image id="kefu-icon" mode="widthFix" src="../../images/kefu.png"></image><text id="kefu-txt">咨询</text></view><button class="button-normal" hover-class="button-hover" bindtap="getUserProfile">购 买</button></view>
Tips:
bindtag按钮点击事件的回调函数名称hover-class按钮选中样式getUserProfile是微信授权接口,固定写法safeAreaHeight是为了适配样式如下:
#bottom-wrap {position: fixed;width: 100%;bottom: 0;z-index: 1;background: #fff;display: flex;flex-direction: row;/* padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom); */}#kefu {position: relative;margin-top: 10rpx;margin-left: 30rpx;width: 200rpx;height: 100rpx;display: flex;flex-direction: row;border: 2rpx solid green;border-radius: 100rpx;background-color: #EEE;justify-content: space-evenly;align-items: center;}#kefu-icon {margin: 0;padding: 0;width: 70rpx;height: 70rpx;}#kefu-txt {margin: 0;padding: 0;font-size: 30rpx;line-height: 30rpx;color: green;}.button-normal {position: relative;padding: 0;margin: 10rpx;margin-right: 30rpx !important;width: 400rpx !important;height: 100rpx;display: flex;flex-direction: row;border-radius: 100rpx;background-color: #FF6400;align-items: center;justify-content: center;font-size: 40rpx;color: #fff;}.button-hover {opacity: 0.75;}
Tips:
constant(safe-area-inset-bottom)和env(safe-area-inset-bottom)是适配iPhoneX底部“Dock”栏的方法。但是适配的高度偏高,这里还是使用自定义高度。position是标签需要经常使用的定位属性,一般常用的是position: relative;相对位置position: absolute;绝对位置,常用于浮动在父级节点上,不会撑起父级容器。position: fixed;固定位置,常用于固定在界面的下方或者上方,不会随着窗体滚动而变化位置。
自定义适配高度,具体计算规则在如下代码中:
const system = wx.getSystemInfoSync();const windowHeight = Math.round(system.windowHeight);const safeArea = system.safeArea && system.safeArea.top > 20 ? system.safeArea : { top: 0 };const safeAreaHeight = safeArea.top / 2;
底部栏需要始终固定在屏幕最下方,所以使用position: fixed
客服按钮,购买按钮
点击客服按钮的回调函数是tapKefu点击购买按钮的回调函数是getUserProfile
微信登录授权
getUserProfile是微信官方提供的接口,用于唤起用户微信授权。可以获得用户的用户名,微信昵称,头像地址等个人信息。
如果用户不同意,那么不会获得相应的数据。
并且该API无法主动调起,必须通过绑定点击事件。
wx.getUserProfile({lang: 'zh_CN',desc: '用于完善用户资料',success: res => {...},fail: err => {...},complete: param => {...}})
这里包含成功,失败,还有完成(无论成功失败都会走的),三个处理函数。可以在这里实现业务逻辑。
底部用户协议,用户协议同意定位
一般界面都会设计诸如 “用户协议”,”个人信息保护声明“,“电信业务经营许可证”,之类的信息。
如下:
<!-- 备案信息 --><view class="question-wrap"><view class="record-wrap"><image class="choose-record"src="{{chooseRecord ? chooseRecordImg : unChooseRecordImg}}"catchtap="tapChoose" /><text>我同意</text><text class="record" catchtap="tapRecord">《个人信息授权及保护声明》</text><text>和</text><text class="record" catchtap="tapRecord">《用户协议》</text></view><text class="question-desc">XXXXXXXX公司 京ICP备123456789号</text></view>
其实就是几个文字和URL组成的。
这里点击授权信息,跳转到一个内嵌的webView界面,显示H5链接。我就暂时写成bai du地址了,可以替换成真实业务地址。
const h5 = '';const url = `../../pages/commonWebView/commonWebView?url=${encodeURIComponent(h5)}&share=false`;wx.navigateTo({ url });
commonWebView页面也很简单,只需要实现对应的回调函数即可,详细代码实现,可下载代码包,仔细查看,这里篇幅有限不再占用。
<web-view src="{{url}}" bindmessage="webViewObserverMessage"bindload="webViewLoadSuccess" binderror="webViewLoadError" />
— NEXT —
3 x 3 按钮组件
在落地页经常需要实现一个可以点击的按钮列表,为用户提供直观的可选产品。
组件使用
创建组件,在根目录下创建components路径。在components下创建组件文件夹,右键文件夹创建组件,文件夹名称和组件名称尽量一致。在需要引用组件的wxml中,以标签形式使用组件。在需要引用组件的json文件usingComponents数组下,添加组件相对路径。这里新建一个<Fruits></Fruits>水果按钮列表组件。
分析界面结构:
纵向三层,整体居中。第一层是标题 “请选择水果”,外加两个小手动画。内部是水平布局。第二层是 3 x 3 按钮列表,里面有文字,有点击事件,有按钮样式,整体居中布局。第三层是一个居中布局的文字。结构很简单,开始动手写wxml
<view class="title-wrap"><image class="finger" src="{{fingerImg}}"/><text>请选择要购买的水果</text><image class="finger" src="{{fingerImg}}"/></view><view class="fruits-list"><view class="fruit" wx:for="{{fruitsList}}" wx:key="index" data-index="{{index}}" catchtap="tapFruit"><text>{{item}}</text><button class="fruit-btn" bindtap="getUserProfile" data-index="{{index}}"></button></view></view><view class="tips">{{'*购买成功记得五星好评哦'}}</view>
样式:
.title-wrap {margin: 40rpx auto;padding: 0;display: flex;flex-direction: row;justify-content: space-between;flex-grow: 1;flex-shrink: 1;font-size: 34rpx;color: #000;font-family: PingFangSC-Semibold,PingFang SC;}.finger {width: 44rpx;height: 50rpx;}.fruits-list {position: relative;margin: 0 50rpx;display: flex;flex-flow: row wrap;}.fruit {position: relative;margin: 0 8rpx 24rpx;width: 200rpx;background: #fff;border: 2rpx solid rgba(255, 107, 44, 1);border-radius: 10rpx;font-size: 28rpx;line-height: 72rpx;font-weight: 600;color: rgba(255, 98, 3, 1);text-align: center;}.fruit:nth-child(3n+1) {margin-left: 0;}.fruit:nth-child(3n) {margin-right: 0;}.fruit-btn {background: transparent;width: 100% !important;height: 100%;z-index: 1;position: absolute;top: 0;bottom: 0;left: 0;right: 0;}.tips {margin: 12rpx 30rpx 38rpx;font-size: 24rpx;font-weight: 300;color: #999999;line-height: 24rpx;letter-spacing: 1rpx;text-align: center;}
Tips:
小手指是上下移动的,可以使用CSS关键帧动画实现按钮列表中,需要绑定按钮序号,使用data-index绑定循环中的序号。节点自适应左右居中常用的方法就是设置margin: 0 auto。这里面用到了flex-grow和flex-shrink。着重简单介绍一下,因为这两个属性经常会用到。
flex-grow
flex-grow处理父元素在还有剩余空间时的分配规则,分为两种情况。
即:所有元素的flex-grow值之和大于1,和小于1。
大于1时,例如:
父元素宽600,子元素A和B,宽分别为200,300。还剩余100。
此时A,B的flex-grow分别为2,3。则剩余100,分给A 2/5,分给B 3/5。
A,B宽度为:
200 + 40 = 240300 + 60 = 360
小于1时,作为分母的总和会引入1来处理。例如:
上例中,A,Bflex-grow分别为 0.2,0.3。则分给A 0.2/1,分给B 0.3/1。
A,B宽度为:
200 + 20 = 220300 + 30 = 330
还剩50没有分配给任何子元素扩张。
另外,flex-grow还会受到父元素的max-width影响。如果grow后的结果超出max-width,max-width会优先生效。
flex-shrink
和flex-grow处理父元素剩余空间相对应的,是flex-shrink处理父元素空间不足时,子元素的收缩规则。
同样分为两种情况,所有元素的flex-shrink值之和大于1,和小于1。
大于1时,例如:
父元素宽度为600,子元素宽度为400,300。超出100。
A,Bflex-shrink分别为 1,2。总权重为400 + 300 * 2 = 1000
A收缩 -100 * 1 * 400 / 1000 = -40B收缩 -100 * 2 * 300 / 1000 = -60
A,B实际宽度为:
400 - 40 = 360300 - 60 = 240
小于1时,例如,
A,Bflex-shrink分别为 0.1,0.2。总权重为400 * 0.1 + 300 * 0.2 = 100
子元素收缩总和为100 * 0.3 / 1 = 30
A收缩 -30 * 0.1 * 400 / 100 = -12B收缩 -30 * 0.2 * 300 / 100 = -18
A,B实际宽度为:
400 - 12 = 388300 - 18 = 282
多出70没有分配给任何子元素收缩。
同样,也会受到min-width的限制。
组件传递数据
父组件向子组件传递数据
在组件的属性列表中新增参数字段:
properties: {option: {type: Boolean,value: true}}
这个属性需要在使用组件的位置赋值,并作为参数传递下去:
<Fruits option="{{true}}"></Fruits>
子组件向父组件传递数据
在子组件内trigger一个事件,然后在子组件被引用的位置bind事件。并且在事件响应函数中,使用传递过来的数据。
Trigger:this.triggerEvent('eventName', { index });
可以在后面夹带参数。
Bind:<Fruits option="{{true}}" bindeventName="callBack"></Fruits>
CallBack:
callBack: function (e) {// 事件传递过来的参数const index = e.detail.index;}
CSS关键帧动画
为了实现手指向下的小动画,使用关键帧处理。
如果在@keyframes规则中指定了CSS样式,动画将在设定时间逐渐从当前样式更改为新样式。
.finger:first-child {margin-right: 10rpx;animation: moveDownLeft .9s infinite;}.finger:last-child {margin-left: 10rpx;animation: moveDownRight .9s infinite;}
左右手各使用一个动画,因为如果使用同一个动画,再Y轴翻转一下也可以。但是会出现左右动画不同时运动的问题。0.9s 是持续时间,infinite是无限循环。
keyframes:
@keyframes moveDownLeft {0% {transform: translateY(0rpx);}50% {transform: translateY(9rpx);}100% {transform: translateY(0rpx);}}@keyframes moveDownRight {0% {transform: translateY(0rpx) scale(-1, 1);}50% {transform: translateY(9rpx) scale(-1, 1);}100% {transform: translateY(0rpx) scale(-1, 1);}}
左手动作设置了从开始到一半,再到结束时的Y轴位移。右手Y轴动作一致,只不过水平翻转一下。
— NEXT —
拼团成功组件
在落地页中加入拼团成功动画。同样也是使用组件实现。
动画效果设计为,开始显示两个人已在团内,另有一个人的头像在拼团成功时飞入第三个头像框,表示拼团成功。同时文字由“即将成团”变成“拼团成功”。并且倒计时持续刷新。拼团成功会有一个标志章显示出来,然后头像和拼团文字整体向上滚动,最后刷新出下一组拼团头像。
最右侧是一个去拼团的点击按钮。
写动画的难点不是动作怎样写,而是整体的节奏感是否协调。
分析界面布局:
纵向布局分三层
第一层标题,里面水平结构,包含三个文字。第二层头像,文字,按钮,另外还有一个飞动的图片。这里头像又可以做成组件。第三层是一个拼团成功的图片。
PinTuan组件
<view class="wrap"><view class="text-wrap"><text>还差</text><text class="persion-num">{{personNum}}人</text><text>成团,可直接参与</text></view><view class="pintuan-content"><PinTuanHead class="pin-tuan" headUrls="{{headUrls}}" animation="{{pinTuanAni}}" bindtransitionend="pinTuanAniEnd" isNeedLogin="{{isNeedLogin}}"></PinTuanHead><view class="join" animation="{{pinTuanAni}}" bindtransitionend="pinTuanAniEnd"><text class="join-text">{{joinText}}</text><text class="clock">还剩{{clockText}}</text></view><image animation="{{headAniData}}" bindtransitionend="headAniEnd" class="move-head" src="{{moveHead}}"></image><button class="goGroup" bindtap="getUserProfile">去参团</button></view><image wx:if="{{pinTuanSuccess}}" class="successed" src="{{successedImg}}" mode="widthFix"animation="{{successAni}}" bindtransitionend="successAniEnd" style="transform: scale(0.3) opacity(0)"></image><view class="bottom-border"></view></view>
animation动画
使用animation动画,可以实现复杂的动作流程。动画的开始和结束都需要处理逻辑。
创建动画后,需要导出一下,代码实现如下:
let ani = wx.createAnimation({delay: 0,duration: 500,timingFunction: 'ease'});ani.opacity(0).translateY(-30).step();this.setData({pinTuanAni: ani.export()});
先设置动画属性,再设计动画运动轨迹,最后导出:
timingFunction: 'ease'
设置缓动效果。ani.opacity(0).translateY(-30).step();
先透明度为0,然后Y轴坐标。step()
表示一组动画完成。可以在一组动画中调用任意多个动画方法,一组动画中的所有动画会同时开始,一组动画完成后才会进行下一组动画。bindtransitionend
是设置动画结束时的回调函数。
代码和样式请下载资源包,对应 PinTuan 文件夹下,因篇幅有限,这里不列出详细代码
PinTuanHead组件
分析界面布局:
三个头像,分为头像背景图,和真实头像图。并且需要动态控制头像显示。
<view class="wrap"><image wx:if="{{person1Show}}" class="icon" src="{{headUrls[0].avatar}}" mode="widthFix"></image><image wx:else class="back" src="{{backImg}}" mode="widthFix"></image><image wx:if="{{person2Show}}" class="icon" src="{{headUrls[1].avatar}}" mode="widthFix"></image><image wx:else class="back" src="{{backImg}}" mode="widthFix"></image><image wx:if="{{person3Show}}" class="icon" src="{{headUrls[2].avatar}}" mode="widthFix"></image><image wx:else class="back" src="{{backImg}}" mode="widthFix"></image></view>
代码和样式在资源包 PinTuanHead 文件夹
Tips:
样式中使用:nth-child表示同类标签的第几个标签。这类伪标签可以节省wxml空间,减少document 渲染的节点数量。— NEXT —
广告轮播
微信提供轮播图组件,可以设置轮播间隔,提示点,循环等属性。
水平广告轮播
设置一组图片水平方向循环轮播
<swiper class="banner-scroll" indicator-dots="{{true}}" indicator-active-color="skyblue" indicator-color="#fff" autoplay="{{true}}" interval="{{5000}}" circular="{{true}}" duration="{{500}}"><block wx:for="{{swiperList}}" wx:key="*this"><swiper-item><image class="box-image" src="{{item}}" mode="widthFix"/></swiper-item></block></swiper>
消息轮播
左上角设置纵向消息轮播。
分析界面布局:
水平布局,左边是用户头像,右边是文字。文字显示分两种情况,如果是带拼团的,就随机显示“刚刚拼团成功”和“刚刚参团成功”文字。如果不带拼团,就显示“刚刚抢单成功”。<view class="recent-payment-list-wrap"><swiper class="recent-payment-list" vertical="{{true}}" autoplay="{{true}}" interval="{{3000}}" circular="{{true}}"duration="{{500}}" capture-catch:touchmove='preventTouchMove'><block wx:for="{{recentPaymentUsers}}" wx:for-index="index" wx:key="index"><swiper-item><view class="recent-payment-cell"><view class="recent-payment-cell-content"><image class="recent-payment-avatar" src="{{item.avatar}}" mode="aspectFill" /><view class="recent-payment-name">{{item.name}}</view><view wx:if="{{showPinTuan}}"><view wx:if="{{item.pinTuanRandom}}" style="flex-shrink: 0;">刚刚拼团成功</view><view wx:else style="flex-shrink: 0;">刚刚参团成功</view></view><view wx:else style="flex-shrink: 0;">刚刚抢单成功</view></view><view style="flex-grow: 1;"></view></view></swiper-item></block></swiper></view>
代码和样式在资源包,landingpage 文件夹下
— NEXT—
下浮层
点击水果按钮,弹出注册手机号下浮层。如果已经注册手机号,弹出订单详情弹窗。
切换下浮层显示通过promptStatus值为0或者1决定。
下浮层封装为组件FruitPrompt,自定义组件的显隐,不能通过设置hidden实现。可以设置wx:if条件判断显示。
为了方便处理下浮层的显示,设置一个浮层基类组件Prompt,FruitPrompt继承自Prompt
Prompt:
<view class='prompt {{slowDown?"hideOpacity":""}}' data-type="mask" catchtap='closeCallback' catchtouchmove='touchMove'><view class='container {{slowDown?"slowDown":""}}' catchtap='catchEvent'><view class='title'>{{title}}</view><image src='{{iconClose}}' class='icon' catchtap='closeCallback' data-type="button" /><slot></slot><view wx:if="{{showButton}}" class='btnContainer'><button class="menuBtn" bindtap="btnCallback">{{btnText}}</button></view></view></view>
注册手机号,获取验证码,验证码倒计时
分析界面布局
纵向布局:
手机号和验证码用到input标签。输入手机号和验证码之后,“获取验证码”按钮高亮,并可以点击。
<block wx:if="{{status === 1}}"><view class="section-title">注册手机号</view><view class="phone-cell"><text>*</text><input type="number" maxlength="11" placeholder="点击输入手机号" class="input" value="{{phoneNumber}}"focus="{{phoneNumberFocus}}" bindinput="phoneInput" /></view><view class="phone-cell"><text>*</text><view class="input-wrap"><input type="number" maxlength="6" placeholder="请输入验证码" placeholder-class="input-placeholder" class="input"value="{{verifyCode}}" bindinput="verifyInput" /><view class="verify-button" hover-class="verify-btn-hover" hover-stay-time="100"style="{{inputCodeButtonStyle}}" catchtap="tapGetVerifyCode">{{inputCodeButtonTitle || '获取验证码'}}</view></view></view><view class="section-title">商品信息</view><view class="order-info-wrap"><view class="order-info-name">自定义文字内容</view><view class="order-info-time">:01:01 00:00-:12:31 00:00</view></view></block>
注册手机号,需要实现验证码功能,点击获取验证码,校验手机号输入合法性。合法则申请验证码,并且进入60s倒计时。
验证码倒计时部分,利用setInterval封装一个公共的倒计时函数,提供异步回调函数。
function initCountdown({isCheck: isCheck = false,name: name,timeTotal: timeTotal,timeInterval: timeInterval,checkCallback: checkCallback,timeChangedCallback: timeChangedCallback,endCallback: endCallback}) {if (typeof name !== 'string' || !name) {return;}const countdownInterval = countdownMap[name];if (countdownInterval) {clearInterval(countdownMap[name].interval);} else if (isCheck) {if (typeof checkCallback === 'function') {checkCallback();}return;} else {countdownMap[name] = {timeTotal: timeTotal,timeInterval: timeInterval};}if (typeof timeChangedCallback === 'function') {timeChangedCallback(countdownMap[name].timeTotal);}countdownMap[name].interval = setInterval(() => {if (countdownMap[name].timeTotal <= 0) {clearInterval(countdownMap[name].interval);delete countdownMap[name];if (typeof endCallback === 'function') {endCallback();}return;}countdownMap[name].timeTotal -= countdownMap[name].timeInterval;if (typeof timeChangedCallback === 'function') {timeChangedCallback(countdownMap[name].timeTotal);}}, countdownMap[name].timeInterval);}
在点击验证码按钮时,触发倒计时。
initCountdownManager(isCheck) {countdownManager.initCountdown({isCheck: isCheck,name: 'bindPhone.verifyCode',timeTotal: 60000,timeInterval: 1000,checkCallback: () => {this.data.countingdown = false;},timeChangedCallback: countdown => {this.setData({inputCodeButtonTitle: `重新发送(${parseInt(countdown / 1000)}s)`,inputCodeButtonStyle: 'color: #CCCCCC;'});},endCallback: () => {this.setData({inputCodeButtonTitle: '重新发送',inputCodeButtonStyle: 'color: #FF8134;'});this.data.countingdown = false;}});}
Tips:
setData函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的this.data的值(同步)。如果更新数据之后,没有使用setData函数例如:this.data.countingdown = false
则只是将数据写入this.data,不能刷新界面显示。
订单详情
设计显示售罄标记,先到先得标记。
分析界面布局:
纵向结构;
水果按钮列表,3 x 3 列表。按钮右上角设置标签。订单介绍,保质期时长。介绍按钮右上角有标签。
<block wx:if="{{status === 0}}"><view class="choose-wrap"><view class="section-title">请选择要购买的水果</view><view class="choose-fruit-list"><viewclass="choose-fruit-item {{fruit.soldout ? 'item-soldout' : '' }} {{fruit.disable ? 'item-disable' : '' }}"wx:for="{{fruits}}" wx:for-item="fruit" wx:for-index="index" wx:key="*this" data-index="{{index}}"catchtap="tapChooseFruit">{{fruit.title}}</view></view><block wx:if="{{!currentFruit.soldout}}"><view class="section-title"><text>选择水果发货时间</text><text class="choose-fruit-tips">新鲜水果,好吃不贵</text></view><scroll-view enable-flex="true" scroll-y class="choose-term-list"><view class="choose-term-item {{term.selected ? 'item-selected' : ''}}" wx:for="{{chooseTerms}}"wx:for-item="term" wx:for-index="termIndex" wx:key="*this" data-index="{{termIndex}}"catchtap="tapChooseTerm"><text>{{term.name}}</text><view class="choose-term-item-tips">{{term.tip}}</view></view></scroll-view></block><block wx:else><view class="soldout-title">该水果已售罄</view><view class="soldout-desc">到货第一时间联系您</view></block></view></block>
代码和样式请下载资源包,对应 FruitPrompt 文件夹下,因篇幅有限,这里不列出详细代码
Notification 监听者模式
处理逻辑,经常需要用到监听者模式。实际原理很简单,只需一个数组,将需要监听的对象和钩子函数压栈,然后在捕获到钩子时,在出栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9cV8cQRN-1633762407320)(soldout.png)]
const observerList = [];// 添加观察者function addObserver(notificationName, selector, target) {const observer = {name: notificationName,target: target,selector: selector}observerList.push(observer);}
// 发送通知function postNotification(notificationName, data = {}) {for (let i = 0; i < observerList.length; i++) {const observer = observerList[i];if (notificationName === observer.name) {observer.selector(data);}}}
— NEXT —
Toast 提示弹窗
封装各种提示弹窗。使用wx.showToast我们再封装一层,可以提示各种自定义信息,也可以加自定义icon。
// 文字提示框function showTextToast(title, cb, seconds, mask = true) {showToast({title: title,icon: 'none',mask: mask,callback: cb,seconds: seconds})}// 加载提示框function showLoadingToast(title, cb, seconds) {showToast({title: title,icon: 'loading',mask: true,callback: cb,seconds: seconds})}// 成功提示框function showSuccessToast(title, cb, seconds) {showToast({title: title,icon: 'success',mask: true,callback: cb,seconds: seconds})}// 错误提示框function showErrorToast(title, cb, seconds) {showToast({title: title,image: 'XXXX',icon: 'none',mask: true,callback: cb,seconds: seconds})}// 文字提示框function showToast({title: title,icon: icon,image: image,mask: mask,callback: callback,seconds: seconds}) {if (!title) {if (callback) {callback()}return;}if (!seconds) {seconds = 1.7;}wx.showToast({title: title,icon: icon,image: image,mask: mask,duration: seconds * 1000});setTimeout(function () {if (callback) {callback()}}, seconds * 1000);}
总结
微信小程序开发,常用标签和style样式并不多,很容易掌握。
常用wxml标签:
view当作节点使用image图片text文字block不占位标签swiper轮播scroll-view滚动层web-viewH5内嵌input输入框button按钮常用style样式
position: relative, absolute, fixed设定节点坐标margin, padding设定节点边距,margin是外边距,padding是内边距display设定元素的显示类型width, height宽高top, bottom, left, right设定absolute坐标后设定上,下,左,右,间距background背景,可以设定颜色,背景图片,背景尺寸z-indexZ 轴优先级font字体,可以设定字体库,字体颜色,阴影,描边,字体大小,字间距,行间距border边框,圆角,自定义边角animation动画,可以设定逐帧动画,也可以绑定动画事件常用技巧
居中:
左右居中:父节点需要设置display:flex;然后子节点设置margin: 0 auto;子节点可以水平左右居中。节点内容居中:同样,也需要父节点设置display:flex;然后父节点再设置align-items: center;可以实现内部元素水平和垂直都居中。文字水平居中:设置父节点text-align: center;可以实现内部文字水平居中。文字垂直居中:text标签font-size和line-height设置一致时,文字垂直居中。依靠元素显示类型居中:display: flex;justify-content: center; // 水平居中vertical-align: middle; // 垂直居中
— NEXT —