微信小程序简单易上手,只要有一些编程基础,即可快速开发基本的项目。
本项目是常见的,商品广告落地页小程序。提供商品浏览,商品列表按钮,购买,微信授权,手机号绑定,验证码校验,用户协议,消息通知,监听者模式等基本功能。
定位学习人群是刚接触微信小程序的,零基础同学。
本人刚接触微信小程序时,也是零基础,HTML和CSS都是第一次接触,经过两个星期的学习,就掌握了基本的开发技巧,并独立完成多个项目。所以即使没有这方面经验的同学也不要气馁,只要学习几个实战项目之后,应付工作基本上都是绰绰有余的。
创建小程序
落地页可适配长度界面
落地页底部栏:
3 x 3 按钮组件
拼团成功组件
广告轮播
下浮层
Notification 监听者模式
Toast 提示弹窗
总结
代码下载
创建一个小程序项目,如果只是学习,那只需要下载安装 微信开发者工具。如果是商用的话,需要申请APPID,并根据自己需要开通相应的功能,例如支付接口,以及申请自己的资源CDN。
— NEXT —
本小节,我们来实现一下落地页可适配长度的滚动界面。
需要创建 page,名称就叫做 landingpage。
在app.json中,添加启动页,输入名称,按下回车,会自动在 pages/ 路径下生成文件夹,并生成 landingpage.js,landingpage.json,landingpage.wxml,landingpage.wxss 四个文件。
{
"pages":[
"pages/landingpage/landingpage",
...
]
}
我习惯先写 .wxml 文件,然后在 .wxss 文件中随时调试界面样式,涉及到引用的组件,在 .json 文件中添加即可。界面逻辑写在 .js 文件中。
先设置 landingpage 整体样式:
.main-wrap {
position: relative;
display: flex;
flex-direction: column;
background: #EEE;
}
使用 wx:for 设置一组图片,wx:key可以写成 *this
图片样式为:
.banner {
width: 100%;
height: auto;
}
这里 bannerImgList 为本地一组图片资源,在 data 中声明:
bannerImgList: [
'../../images/landingpage1.jpg',
'../../images/landingpage6.jpg',
'../../images/landingpage7.jpg',
'../../images/landingpage8.jpg',
]
这样,落地页基本就有了一个简单的界面,图片从上到下铺满屏幕。
注意这里的图片在实际项目中,需要使用CDN的下载地址,不然本地资源太多,影响小程序加载速度,而且上传小程序也有尺寸限制。
— NEXT —
本小节,我们布局底部栏,包含用户协议,和两个按钮。
先创建一个bottom容器:
.bottom-box {
position: relative;
width: 100%;
height: 120rpx;
}
再添加两个按钮和文字:
咨询
样式如下:
#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;
}
自定义适配高度,具体计算规则在如下代码中:
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;
wx.getUserProfile({
lang: 'zh_CN',
desc: '用于完善用户资料',
success: res => {
...
},
fail: err => {
...
},
complete: param => {
...
}
})
这里包含成功,失败,还有完成(无论成功失败都会走的),三个处理函数。可以在这里实现业务逻辑。
一般界面都会设计诸如 “用户协议”,”个人信息保护声明“,“电信业务经营许可证”,之类的信息。
如下:
我同意
《个人信息授权及保护声明》
和
《用户协议》
XXXXXXXX公司 京ICP备123456789号
其实就是几个文字和URL组成的。
这里点击授权信息,跳转到一个内嵌的 webView 界面,显示 H5 链接。我就暂时写成 bai du 地址了,可以替换成真实业务地址。
const h5 = 'www.baidu.com';
const url = `../../pages/commonWebView/commonWebView?
url=${encodeURIComponent(h5)}&share=false`;
wx.navigateTo({ url });
commonWebView页面也很简单,只需要实现对应的回调函数即可,详细代码实现,可下载代码包,仔细查看,这里篇幅有限不再占用。
— NEXT —
在落地页经常需要实现一个可以点击的按钮列表,为用户提供直观的可选产品。
结构很简单,开始动手写 wxml
请选择要购买的水果
{{item}}
{{'*购买成功记得五星好评哦'}}
样式:
.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;
}
这里面用到了 flex-grow 和 flex-shrink。着重简单介绍一下,因为这两个属性经常会用到。
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 = 240
300 + 60 = 360
小于1时,作为分母的总和会引入1来处理。例如:
上例中,A,B flex-grow 分别为 0.2,0.3。则分给A 0.2/1,分给B 0.3/1。
A,B宽度为:
200 + 20 = 220
300 + 30 = 330
还剩50没有分配给任何子元素扩张。
另外,flex-grow 还会受到父元素的 max-width 影响。如果grow后的结果超出 max-width,max-width 会优先生效。
和 flex-grow 处理父元素剩余空间相对应的,是 flex-shrink 处理父元素空间不足时,子元素的收缩规则。
同样分为两种情况,所有元素的 flex-shrink 值之和大于1,和小于1。
大于1时,例如:
父元素宽度为600,子元素宽度为400,300。超出100。
A,B flex-shrink 分别为 1,2。总权重为 400 + 300 * 2 = 1000
A收缩 -100 * 1 * 400 / 1000 = -40
B收缩 -100 * 2 * 300 / 1000 = -60
A,B实际宽度为:
400 - 40 = 360
300 - 60 = 240
小于1时,例如,
A,B flex-shrink 分别为 0.1,0.2。总权重为 400 * 0.1 + 300 * 0.2 = 100
子元素收缩总和为 100 * 0.3 / 1 = 30
A收缩 -30 * 0.1 * 400 / 100 = -12
B收缩 -30 * 0.2 * 300 / 100 = -18
A,B实际宽度为:
400 - 12 = 388
300 - 18 = 282
多出70没有分配给任何子元素收缩。
同样,也会受到min-width的限制。
在组件的属性列表中新增参数字段:
properties: {
option: {
type: Boolean,
value: true
}
}
这个属性需要在使用组件的位置赋值,并作为参数传递下去:
在子组件内 trigger 一个事件,然后在子组件被引用的位置 bind 事件。并且在事件响应函数中,使用传递过来的数据。
Trigger: this.triggerEvent('eventName', { index });
可以在后面夹带参数。
Bind:
CallBack:
callBack: function (e) {
// 事件传递过来的参数
const index = e.detail.index;
}
为了实现手指向下的小动画,使用关键帧处理。
如果在 @keyframes 规则中指定了 CSS 样式,动画将在设定时间逐渐从当前样式更改为新样式。
.finger:first-child {
margin-right: 10rpx;
animation: moveDownLeft .9s infinite;
}
.finger:last-child {
margin-left: 10rpx;
animation: moveDownRight .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 —
在落地页中加入拼团成功动画。同样也是使用组件实现。
动画效果设计为,开始显示两个人已在团内,另有一个人的头像在拼团成功时飞入第三个头像框,表示拼团成功。同时文字由“即将成团”变成“拼团成功”。并且倒计时持续刷新。拼团成功会有一个标志章显示出来,然后头像和拼团文字整体向上滚动,最后刷新出下一组拼团头像。
最右侧是一个去拼团的点击按钮。
写动画的难点不是动作怎样写,而是整体的节奏感是否协调。
纵向布局分三层
还差
{{personNum}}人
成团,可直接参与
{{joinText}}
还剩{{clockText}}
使用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 文件夹
— NEXT —
微信提供轮播图组件,可以设置轮播间隔,提示点,循环等属性。
设置一组图片水平方向循环轮播
左上角设置纵向消息轮播。
{{item.name}}
刚刚拼团成功
刚刚参团成功
刚刚抢单成功
代码和样式在资源包,landingpage 文件夹下
— NEXT—
点击水果按钮,弹出注册手机号下浮层。如果已经注册手机号,弹出订单详情弹窗。
切换下浮层显示通过 promptStatus 值为0或者1决定。
下浮层封装为组件 FruitPrompt,自定义组件的显隐,不能通过设置 hidden 实现。可以设置 wx:if 条件判断显示。
为了方便处理下浮层的显示,设置一个浮层基类组件 Prompt,FruitPrompt 继承自 Prompt
Prompt:
{{title}}
纵向布局:
注册手机号
*
*
{{inputCodeButtonTitle || '获取验证码'}}
商品信息
自定义文字内容
2021:01:01 00:00-2021:12:31 00:00
注册手机号,需要实现验证码功能,点击获取验证码,校验手机号输入合法性。合法则申请验证码,并且进入 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;
}
});
}
this.data.countingdown = false
则只是将数据写入 this.data,不能刷新界面显示。设计显示售罄标记,先到先得标记。
纵向结构;
请选择要购买的水果
{{fruit.title}}
选择水果发货时间
新鲜水果,好吃不贵
{{term.name}}
{{term.tip}}
该水果已售罄
到货第一时间联系您
代码和样式请下载资源包,对应 FruitPrompt 文件夹下,因篇幅有限,这里不列出详细代码
处理逻辑,经常需要用到监听者模式。实际原理很简单,只需一个数组,将需要监听的对象和钩子函数压栈,然后在捕获到钩子时,在出栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 —
封装各种提示弹窗。使用 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 样式并不多,很容易掌握。
display: flex;
justify-content: center; // 水平居中
vertical-align: middle; // 垂直居中
— NEXT —