微信小程序实例-历史上的今天
目录:
一、 小程序相关知识
(1) 文件类型
(2) 目录结构
(3) 组件的使用
(4) 基本语法
(5) 相关API(Application Programming Interface)
二、 开发前准备
三、 开发实战
(1)界面设计
(2)界面实现
(3)数据处理
自从微信小程序正式发布以来,涌现出了大量优秀的作品,如“智行火车票”、“摩拜单车”、“知乎热榜”、“识图取字”等等。对于勇于走在技术前沿和热爱尝试新鲜事物的开发者们来说,当然也会开发一款属于自己的小程序。本篇教程就用一个简单的小例子来教大家如何开发一款小程序。
适合读者:对网页前端开发有基本了解的小程序开发小白
小程序名称:历史上的今天
模仿效果(其他小程序):
所需资料:
- 电脑一台(Windows 或 Mac OS 系统均可)
- 小程序开发者账号
- “历史上的今天” API 接口
具体步骤:
一、 小程序相关知识
(1) 文件类型
小程序中的文件可分为4种类型,后缀名分别为“.wxml ”、“.wxss ”、“.js ”、“.json”。其中,后缀名为 “wxml” 的文件为各个页面的布局文件,描述页面所包含的具体内容,类似 Web 编程中的 html 文件。如以下新建项目的首页默认代码:
<!--index.wxml-->
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
效果预览图:
image.png
.wxss后缀的则类似css文件,语法上虽和css基本相同,但也做了一些扩充和修改:
- 新增了尺寸单位“rpx”。“rpx”是微信小程序中css的尺寸单位,可以根据屏幕宽度进行自适应。
- 提供了全局的样式和局部样式。项目中的 app.wxss 为全局样式文件,会作用于使用了该文件样式的所有页面,而局部页面样式 page.wxss 仅对当前页面生效。
/**index.wxss**/
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}
.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
image.png
js 文件用来处理程序的交互和逻辑,如响应用户的点击、获取用户的位置、进行网络请求等等。其中的 Page()函数,用来注册一个页面,指定页面的初始数据(data),另外还包括生命周期函数(onLoad、onReady、onShow、onHide、onUnload),事件处理函数(bindViewTap)等等。
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
motto: 'Hello World',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse){
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function(e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
}
})
json文件为小程序的配置文件,分为page.json、app.json 和 project.config.json三种。
- app.json——是对当前小程序的全局配置,如小程序的所有页面路径、导航栏背景颜色、网络超时时间、底部 tab 菜单等。其中“pages”字段用于描述当前小程序所有页面路径;所有页面的顶部背景颜色,文字颜色在window字段中定义;底部 tab 菜单则在“tabBar”中定义。
- page.json——每个页面都会有个单独的配置文件,当某个页面需要进行个性化配置时,例如设置不同的导航栏背景色,则需在该页面的json文件中进行设置。
- project.config.json——项目的配置文件,其中保存的是开发者对编译器的个性化配置,例如界面颜色、文字大小、编译配置等等。
//app.json
{
"pages": [
"pages/index/index",
"pages/logs/index"
],
"window": {
"navigationBarTitleText": "Demo"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页"
}, {
"pagePath": "pages/logs/logs",
"text": "日志"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true
}
(2) 目录结构
新建项目后的默认文件目录如下图所示。最外层的“pages”为小程序中各个页面的所在文件夹,其中的每个子文件夹都对应着不同的页面,如图中的“index”和“log”文件夹分别对应着小程序的“首页”和“日志”两个页面。
顾名思义,“utils”为工具类文件夹,开发者可以将与字符串处理、日期处理或网络请求有关的代码文件放在该文件夹中。
最外层剩余的四个文件则依次为“app.js”、“app.json”、“app.wxss”、“project.config.json”,均是对小程序进行全局属性设置的文件。
q.png
(3)组件的使用
组件是视图层的基本组成单元。如手机应用中常见的文本、按钮、输入框、图片、滑动选择器等。小程序为开发者提供了一系列基础组件,自带一些与微信风格类似的样式,开发者可以通过组合这些基础组件进行快速开发。
一个组件通常包括开始标签和结束标签,属性用来修饰这个组件,内容在两个标签之内。如按钮组件“button”:
<button>点击设置以上按钮disabled属性</button>
官方为开发者提供了详细的小程序组件说明及使用帮助文档,本例中我们只需其中的 3 个组件即可实现相关功能。
-
view:视图容器,类似html中的div。
示例代码:
<view style="height: 300px;line-height:300px;background-color:#F76160;color:white;text-align:center;">
历史上的今天
</view>
效果图:
-
scroll-view:可滚动视图区域。
2018-01-19_223723.png
注:使用竖向滚动时,需要给<scroll-view/>一个固定高度。
<!--pages/scrollview/scrollview.wxml-->
<view >
<!--垂直滚动-->
<view >垂直滚动</view>
<scroll-view scroll-y style="height: 200px;" >
<view id="green" style="width:100%;height:100px;background-color:green;">green</view>
<view id="red" style="width:100%;height:50px;background-color:red;">red</view>
<view id="yellow" style="width:100%;height:100px;background-color:yellow;">yellow</view>
<view id="blue" style="width:100%;height:50px;background-color:blue;">blue</view>
</scroll-view>
<!--水平滚动-->
<view style="margin-top:50px;">水平滚动</view>
<scroll-view scroll-x="true" style=" white-space: nowrap; display: flex" >
<!-- display: inline-block-->
<view style="background: green; width: 150px; height: 100px; display: inline-block">green</view>
<view style="background: red; width: 80px; height: 100px; display: inline-block" >red</view>
<view style="background: yellow; width: 150px; height: 100px; display: inline-block">yellow</view>
<view style="background: blue; width: 80px; height: 100px; display: inline-block">blue</view>
</scroll-view>
</view>
1.gif
-
text:文本。
示例代码:
<text style="color:red;font-size:56rpx;">
微信小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
</text>
效果图:
(4)基本语法
① 变量定义及使用
小程序中的变量定义在对应页面 js 文件里的 Page - data 中,在wxml页面使用的时候需用双大括号包裹起来。如下所示:
<!--pages/variable/variable.wxml-->
<view style='width:100%;margin-top:100px;text-align:center;'>
<text>当前时间:{{curTime}}</text>
<button type="primary" style='margin:50rpx;' bindtap='refreshTime'>点击更新时间</button>
</view>
// pages/variable/variable.js
Page({
data: {
curTime:""
},
refreshTime: function () {
var now = new Date();
var h = now.getHours();
var m = now.getMinutes();
var s = now.getSeconds();
var time = h + ':' + m + ':' + s;
//改变变量的值
this.setData({
curTime:time
});
},
})
效果预览图如下:
2.gif
② for循坏
当需要实现类似列表的效果时,可在组件上使用 “wx:for” 语句来重复渲染。默认数组的当前项的下标变量名默认为index,数组当前项的变量名默认为item,当然也可以改成自己想要使用的变量名,如:wx:for-item="phone" wx:for-index="idx" 。
<!--pages/for/for.wxml-->
<view wx:for="{{['苹果','华为','小米','魅族']}}" wx:for-item="phone" wx:for-index="idx" style="text-align:center;margin-top:50px;font-size:30px;">
{{phone}}
</view>
image.png
③ 点击事件
在组件开始标签中添加“ bindtap="click" ”,并在js函数中实现该方法,可为其添加点击事件。
<button type="primary" style='margin:50rpx;' bindtap='click' >点击事件</button>
传参:
当需要获取点击对象的某些属性数据时,可在其开始标签中添加“data-参数名="参数值"”(去掉最外层引号),然后在点击事件中使用“e.currentTarget.dataset.参数名”获取相应的值。
示例代码:
<!--pages/click/click.wxml-->
<view style='width:100%;margin-top:100px;text-align:center;'>
<text>这是:{{name}}</text>
<button type="primary" style='margin:50rpx;' bindtap='click' data-phone="iPhone">苹果</button>
<button type="primary" style='margin:50rpx;' bindtap='click' data-phone="Huawei">华为</button>
<button type="primary" style='margin:50rpx;' bindtap='click' data-phone="Xiaomi">小米</button>
</view>
// pages/click/click.js
Page({
data: {
name:""
},
click:function(e){
var phone = e.currentTarget.dataset.phone;
this.setData({
name:phone
});
}
})
效果预览图: 6.gif
(5)相关API(Application Programming Interface)
① wx.request(OBJECT)
发起网络请求。需要注意的是,在使用该 API 前需在微信公众平台小程序的后台中对服务器域名进行配置,具体操作步骤前面已说明,此处不再赘述。
示例代码:
<!--pages/network/network.wxml-->
<view style='padding:20rpx;'>
<text>{{str}}</text>
<button type="primary" style='margin:50rpx;' bindtap='getRequestStr'>点击进行网络请求</button>
</view>
// pages/network/network.js
Page({
data: {
str:"测试文本"
},
getRequestStr:function(){
var that=this;
wx.request({
url: 'http://www.ipip5.com/today/api.php?type=txt', //仅为示例,并非真实的接口地址
header: {
'content-type': 'application/json' // 默认值
},
success: function (res) {
console.log(res.data);
that.setData({
str: res.data
});
}
})
}
})
效果预览图:
3.gif
② wx.navigateTo(OBJECT)
跳转到应用内的某个页面。
OBJECT 参数说明:
使用方法(navigate2为要跳转的页面):
wx.navigateTo({
url: 'navigate2'
})
很多时候,跳转的页面之间需要进行参数的传递。比如“淘宝”APP中,当搜索关键词“手机”的时候,如果用户对某款手机感兴趣,会点击查看它的具体参数配置,那么在跳转后的页面中就需要知道用户到底点击了哪一项,也就是选择了哪款手机。
小程序页面之间的参数传递方法与Web开发类似,在要跳转的 url 后加上“?”,后跟参数名和参数值,多个参数之间用“&”号连接。
wx.navigateTo({
url: 'navigate2?phone=xiaomi'
})
然后在跳转后的页面中使用“options.参数名”获取传过来的参数值:
onLoad: function (options) {
var phone = options.phone;
console.log(phone); //xiaomi
}
示例代码:
navigate 页面:
<!--pages/navigate/navigate.wxml-->
<button type="primary" style='margin:50rpx;' bindtap='toNext'>点击跳转页面</button>
// pages/navigate/navigate.js
Page({
toNext: function (e) {
var toUrl = 'navigate2?phone=小米';
wx.navigateTo({
url: toUrl
})
}
})
navigate2 页面:
<!--pages/navigate/navigate2.wxml-->
<view style='width:100%;margin-top:100px;text-align:center;'>
这是:{{mPhone}}
</view>
// pages/navigate/navigate2.js
Page({
data: {
mPhone:""
},
onLoad: function (options) {
var phone = options.phone;
this.setData({
mPhone:phone
});
}
})
效果预览图:
7.gif
③ wx.getSystemInfo(OBJECT)
获取设备信息。
OBJECT参数说明:
示例代码:
wx.getSystemInfo({
success: function(res) {
console.log(res.model) //手机型号
console.log(res.pixelRatio) //设备像素比
console.log(res.windowWidth) //可使用窗口宽度
console.log(res.windowHeight) //可使用窗口高度
console.log(res.language) //微信设置的语言
console.log(res.version) //微信版本号
console.log(res.platform) //客户端平台
}
})
更多使用方法可查看这里。
二、 开发前准备
工欲善其事,必先利其器。为能顺利完成这个实例,首先需要准备一个小程序的开发者账号,详细步骤可查看这里;小程序所需的数据可从以下
API 接口获得(本实例使用的数据为 json 格式)。
txt格式:http://www.ipip5.com/today/api.php?type=txt
json格式:http://www.ipip5.com/today/api.php?type=json
返回数据示例:
{
"today": "0167081765e5",
"result": [
{
"year": "395\n",
"title": "7f579a6c5e1d56fd520688c24e3a897f7f579a6c5e1d56fd548c4e1c7f579a6c5e1d56fd"
},
{
"year": "1600\n",
"title": "52674f5c5bb653615c145fb7968600b75df45c14536151fa751f"
},
{
"year": "1676\n",
"title": "6b4c52674f5c66f25bb65f176717520765af79d100b7536174e65229901d4e16"
},
{
"year": "1706\n",
"title": "7f8e56fd653f6cbb5bb6672c6770660e00b75bcc5170514b679751fa751f4e8e7f8e56fd9ebb77016ce258eb987f"
}
]
}
在小程序中使用网络相关的 API 时,需要事先在微信公众平台中对服务器域名进行配置。
2018-01-19_172737.png
使用管理员手机微信扫描二维码验证后,在“requests合法域名”中填写 API 的一级域名并保存。
image.png
※注:设置完后需重新启动微信开发者工具。
三、 开发实战
(1)界面设计
在动手之前,先对小程序的界面进行简单的设计。
home.png
点击首页中的每一项,可跳转到当年这一天的事件详情页面。
page_1.png
(2)界面实现
目录结构:新建项目后,首先要将本实例需要的所有页面创建出来。项目目录结构如下:
image.png导航栏标题:导航栏的标题可在“app.json”文件中进行设置,将 “window”-“navigationBarTitleText” 更改为“历史上的今天”。代码如下:
//app.json
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "历史上的今天",
"navigationBarTextStyle": "black"
}
}
首页:整个页面分为上、下两大部分,分别为上方的“日期”及下方的“事件列表”,中间使用分隔线隔开。由于下面的事件列表是可以滚动的(当当天事件足够多的时候),因此需要用到scroll-view。
此时index.wxml的代码如下:
<view style="width: 100%;height:100px;background-color: red;">
</view>
<scroll-view style='width: 100%;height:300px;background-color:blue;' scroll-y>
</scroll-view>
效果预览图如下:
image.png
这里有一个问题就是,底部的滚动视图需要占据所有的剩余高度,但是由于用户的不同,各个机型的屏幕宽高是不一样的,因此需要动态计算小程序的可使用窗口高度,再减去顶部的红色视图高度,即可得到滚动视图的高度。
<!-- index.wxml -->
<view style="width: 100%;height:100px;background-color: red;">
</view>
<scroll-view style='width: 100%;height:{{scrollViewHeight}}px;background-color:blue;' scroll-y>
</scroll-view>
//index.js
Page({
data: {
scrollViewHeight:200
},
onLoad: function () {
var that=this;
//获取屏幕高度
wx.getSystemInfo({
success: function (res) {
that.setData({
scrollViewHeight: res.windowHeight - 100
});
}
});
}
})
效果预览图:
image.png
onLoad函数中之所以要用 “var that=this”,是因为在 javascript 语言中,this代表着当前的对象,它在程序中随着执行的上下文随时会变化。在本例中回调函数对象相对于getSystemInfo点击事件函数对象已经发生了变化。所以已经不是原来的页面对象了。解决的办法就是复制一份当前的对象。所以我们有了这个重要的语句:
var that=this;//把this对象复制到临时变量that.
然后添加下方“事件列表”部分的代码:
index.wxml 页面代码如下:
<!-- index.wxml -->
<!-- 顶部日期 start -->
<view style="width: 100%;height:100px;color:#67CDCD;font-size:44rpx;font-weight:bold;line-height:100px;text-align:center;">
13月32日
</view>
<!-- 顶部日期 end -->
<!-- 中间分隔线 start -->
<view style="width:100%;height:1rpx;background-color:gray;"></view>
<!-- 中间分隔线 end -->
<!-- 底部滚动视图 start -->
<scroll-view style="width: 100%;height:{{scrollViewHeight}}px;" scroll-y>
<view wx:for="{{[1,2,3,4,5,6,7,8,9,10]}}" wx:for-item="item" wx:for-index="idx">
<view style="height:1rpx;background-color:gray;margin:0 20rpx;"></view>
<view style="height:100rpx;line-height:100rpx;padding-left:20rpx;text-overflow: ellipsis;white-space: nowrap; overflow: hidden;"
bindtap='viewDetail' data-year="{{item.year}}" data-title="{{item.title}}">
2030年, iPhone 30 正式发布
</view>
</view>
</scroll-view>
<!-- 底部滚动视图 end -->
微信图片_20180119213450.jpg
点击各个事件,可跳转到显示事件详情的detail页面。
detail.wxml 页面代码如下:
<!--pages/detail/detail.wxml-->
<!-- 顶部日期 start -->
<view style="width: 100%;height:100px;color:#67CDCD;font-size:44rpx;font-weight:bold;line-height:100px;text-align:center;">
2030年13月32日
</view>
<!-- 顶部日期 end -->
<!-- 中间分隔线 start -->
<view style="width:100%;height:1rpx;background-color:gray;"></view>
<!-- 中间分隔线 end -->
<!-- 底部视图 start -->
<view style="padding:20rpx;height:{{viewHeight}}px;" >
iPhone 30 正式发布
</view>
<!-- 底部视图 end -->
微信图片_20180119225935.jpg
(3)数据处理
最后只要获取 API 接口中的数据,并将其显示到界面上就可以了。
//发送请求
wx.request({
url: "https://www.ipip5.com/today/api.php?type=json",
header: {
"content-type": "application/x-www-form-urlencoded"
},
method: 'POST',
success: function (res) {
try {
that.setData({
items:res.data.result,
dateOfToday: res.data.today
});
} catch (e) {
}
wx.hideLoading();
},
fail: function (err) {
console.log("error:" + err);
wx.hideLoading();
}
});
最终源码如下:
index页面:
<!-- index.wxml -->
<!-- 顶部日期 start -->
<view style="width: 100%;height:100px;color:#67CDCD;font-size:44rpx;font-weight:bold;line-height:100px;text-align:center;">
{{dateOfToday}}
</view>
<!-- 顶部日期 end -->
<!-- 中间分隔线 start -->
<view style="width:100%;height:1rpx;background-color:gray;"></view>
<!-- 中间分隔线 end -->
<!-- 底部滚动视图 start -->
<scroll-view style="width: 100%;height:{{scrollViewHeight}}px;" scroll-y>
<view wx:for="{{items}}" wx:for-item="item" wx:for-index="idx">
<view style="height:1rpx;background-color:gray;margin:0 20rpx;"></view>
<view style="height:100rpx;line-height:100rpx;padding-left:20rpx;text-overflow: ellipsis;white-space: nowrap; overflow: hidden;" bindtap='viewDetail' data-year="{{item.year}}" data-title="{{item.title}}">
{{item.year}} {{item.title}}
</view>
</view>
</scroll-view>
<!-- 底部滚动视图 end -->
//index.js
Page({
data: {
scrollViewHeight:200,
dateOfToday:'',
items:[]
},
onLoad: function () {
var that = this;
//显示加载动画
wx.showLoading({
title: '正在加载',
});
//获取屏幕高度
wx.getSystemInfo({
success: function (res) {
console.log(res.windowHeight);
that.setData({
scrollViewHeight: res.windowHeight - 100
});
}
});
//获取当前日期
var myDate = new Date();
var dateStr = myDate.getFullYear() + '年' + (myDate.getMonth()+1) + '月' + myDate.getDate() + '日';
this.setData({
dateOfToday: dateStr
});
//发送请求
wx.request({
url: "https://www.ipip5.com/today/api.php?type=json",
header: {
"content-type": "application/x-www-form-urlencoded"
},
method: 'POST',
success: function (res) {
console.log("success:" + res);
try {
that.setData({
items:res.data.result,
dateOfToday: res.data.today
});
} catch (e) {
}
wx.hideLoading();
},
fail: function (err) {
console.log("error:" + err);
wx.hideLoading();
}
});
},
viewDetail: function (e) {
var title = e.currentTarget.dataset.title;
var year = e.currentTarget.dataset.year;
var url = '../detail/detail?title=' + title + '&year=' + year + '&date=' + this.data.dateOfToday;
wx.navigateTo({
url: url
})
},
})
detail页面:
<!--pages/detail/detail.wxml-->
<!-- 顶部日期 start -->
<view style="width: 100%;height:100px;color:#67CDCD;font-size:44rpx;font-weight:bold;line-height:100px;text-align:center;">
{{dateOfToday}}
</view>
<!-- 顶部日期 end -->
<!-- 中间分隔线 start -->
<view style="width:100%;height:1rpx;background-color:gray;"></view>
<!-- 中间分隔线 end -->
<!-- 底部视图 start -->
<view style="width: 100%;height:{{viewHeight}}px;" >
{{detail}}
</view>
<!-- 底部视图 end -->
// pages/detail/detail.js
Page({
/**
* 页面的初始数据
*/
data: {
dateOfToday:"",
detail:""
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log("options:" + options);
var year = options.year;
var date = options.date;
var title = options.title;
this.setData({
dateOfToday: year + "年" + date,
detail: title
});
}
})