在智能手机软件的装机量中,天气预报类的APP排在比较靠前的位置。说明用户对天气的关注度很高。因为人们无论是工作还是度假旅游等各种活动都需要根据自然天气来安排。本文将带大家开发一个“微天气”小程序,方便微信网友随时查看天气。
要开发天气预报类APP,首先要考虑的问题就是天气预报数据的来源。有了天气预报的数据来源,才能按需要在微信小程序中显示出来。其实,微信小程序就是一个显示天气信息的前端系统,而天气预报API就是后端系统。由于天气预报API可以在网上免费获取,因此,本案例中开发者不需要开发后端系统,只需要根据API的要求进行访问即可。
目前,互联网上提供的天气预报API比较多,笔者将几个主要的API列举出来,读者可根据需要使用(当然,本案例只使用其中一个即可)。
要查询天气预报,当然是以中央气象台的数据为权威。中央气象台通过“中国天气”网站对外发布全国各地天气预报,国内各大门户网站的天气预报数据都是从这个网站获取的。如图1所示就是中国气象局的天气预报网站——中国天气网。
图1 中国天气网
在图1所示的中国天气网中可查看全国乃至世界各地的天气预报信息,在上方查询输入框中输入一个城市名称进行查询,就可查看到详细天气预报数据。例如,输入“上海”单击右端的查询按钮,就可看到如图2所示的上海当天的详细预报。
从图2所示浏览器的地址栏可看到其地址为:
http://www.weather.com.cn/weather1d/101020100.shtml#search
这个URL地址中的101020100是上海的一个编码,如果换成其他城市(如101270101——成都的编码),则可看到其他城市的天气预报信息。
图2 查询上海的天气预报
对于城市编码这个数据,可以从网站上收集到,将其保存到一个文本文件中,查询时从文件中读入即可。例如,将收集到的城市编码按以下格式保存到city.txt文件中。
北京,101010100|北京海淀,101010200|北京朝阳,101010300|北京顺义,101010400|北京怀柔,101010500|北京通州,101010600|北京昌平,101010700|北京延庆,101010800|北京丰台,101010900|北京石景山,101011000|北京大兴,101011100|北京房山,101011200|北京密云,101011300|北京门头沟,101011400|北京平谷,101011500|上海,101020100|上海闵行,101020200|上海宝山,101020300|上海嘉定,101020500|……
在上面的数据格式中,每一个区域名称和编码之间用逗号分隔,而区域之间用竖线分隔。这样做的好处是可用Python中的split函数分隔数据,具体方法详见后面的代码。
知道城市编码后,就可通过城市编码去访问对应的网页,得到该城市的天气预报数据。首先想到的方法当然是用wx.request方法打开对应的网页,获取HTML数据,然后进行分析。不过,这里对HTML进行分析的过程非常麻烦,且效率不高。
不过,中国天气网提供了专用的数据接口,通过访问这些数据接口API,可获得JSON数据。这样,就不会有其他杂乱的HTML代码来干扰。而微信小程序对JSON数据是可以直接解析的,因此,使用这些API接口是最方便的。
1. 天气实况信息
要获取天气实况信息,可通过以下接口:
http://www.weather.com.cn/data/sk/101010100.html
其中,数字部分是城市编码,101010100是北京的编码,因此,上面的接口查询到的是北京的天气实况信息(如果换成101020100,则返回的是上海的天气实况信息)。
在浏览器中输入以上URL地址,可得到如图3所示的结果。
图3 查询北京的天气预报
图3所示返回的是JSON数据,不过,这里作为文本显示,不太容易看得清,整理一下格式,得到的JSON数据如下所示:
{
"weatherinfo": {
"city": "北京",
"cityid": "101010100",
"temp": "18",
"WD": "东南风",
"WS": "1级",
"SD": "17%",
"WSE": "1",
"time": "17:05",
"isRadar": "1",
"Radar": "JC_RADAR_AZ9010_JB",
"njd": "暂无实况",
"qy": "1011",
"rain": "0"
}
}
可看出,返回的JSON对象中有一个weatherinfo对象,其中的各属性分别表示了天气预报中的一项信息,如city是城市名称,temp是当前温度,WD风向,WS是风速……
2. 全天天气预报
要获取全天天气预报的信息,可通过以下接口:
http://www.weather.com.cn/data/cityinfo/101010100.html
其中,数字部分是城市编码,101010100是北京的编码,因此,上面的接口查询到的是北京的天气信息。访问该接口返回的JSON数据如下所示:
{
"weatherinfo": {
"city": "北京",
"cityid": "101010100",
"temp1": "-2℃",
"temp2": "16℃",
"weather": "晴",
"img1": "n0.gif",
"img2": "d0.gif",
"ptime": "18:00"
}
}
3.天气详情
使用以下接口可获取最详尽的天气预报信息。
http://m.weather.com.cn/data/101010100.html
以上接口返回的JSON数据格式如下:
{
"weatherinfo": {
"city": "北京",
"city_en": "beijing",
"date_y": "2016年11月16日",
"date": "",
"week": "星期四",
"fchh": "11",
"cityid": "101010100",
"temp1": "2℃~-7℃",
"temp2": "1℃~-7℃",
"temp3": "4℃~-7℃",
"temp4": "7℃~-5℃",
"temp5": "5℃~-3℃",
"temp6": "5℃~-2℃",
"tempF1": "35.6℉~19.4℉",
"tempF2": "33.8℉~19.4℉",
"tempF3": "39.2℉~19.4℉",
"tempF4": "44.6℉~23℉",
"tempF5": "41℉~26.6℉",
"tempF6": "41℉~28.4℉",
"weather1": "晴",
"weather2": "晴",
"weather3": "晴",
"weather4": "晴转多云",
"weather5": "多云",
"weather6": "多云转阴",
"img1": "0",
"img2": "99",
"img3": "0",
"img4": "99",
"img5": "0",
"img6": "99",
"img7": "0",
"img8": "1",
"img9": "1",
"img10": "99",
"img11": "1",
"img12": "2",
"img_single": "0",
"img_title1": "晴",
"img_title2": "晴",
"img_title3": "晴",
"img_title4": "晴",
"img_title5": "晴",
"img_title6": "晴",
"img_title7": "晴",
"img_title8": "多云",
"img_title9": "多云",
"img_title10": "多云",
"img_title11": "多云",
"img_title12": "阴",
"img_title_single": "晴",
"wind1": "北风3-4级转微风",
"wind2": "微风",
"wind3": "微风",
"wind4": "微风",
"wind5": "微风",
"wind6": "微风",
"fx1": "北风",
"fx2": "微风",
"fl1": "3-4级转小于3级",
"fl2": "小于3级",
"fl3": "小于3级",
"fl4": "小于3级",
"fl5": "小于3级",
"fl6": "小于3级",
"index": "冷",
"index_d": "天气冷,建议着棉衣、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣或冬大衣。",
"index48": "冷",
"index48_d": "天气冷,建议着棉衣、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣或冬大衣。",
"index_uv": "弱",
"index48_uv": "弱",
"index_xc": "适宜",
"index_tr": "一般",
"index_co": "较不舒适",
"st1": "1",
"st2": "-8",
"st3": "2",
"st4": "-4",
"st5": "5",
"st6": "-5",
"index_cl": "较不宜",
"index_ls": "基本适宜",
"index_ag": "极不易发"
}
}
不过,现在中国天气网已不能通过这个接口获取数据了。
中华万年历的天气预报接口地址如下:
http://wthrcdn.etouch.cn/weather_mini?city=北京
该接口很简单,只需要给出城市的名称即可,不像中囯天气网的接口还需要根据城市名称去查询城市编码,然后再去访问接口。接口返回的数据也是JSON格式,具体形式如下所示:
{
"desc": "OK",
"status": 1000,
"data": {
"wendu": "15",
"ganmao": "昼夜温差较大,较易发生感冒,请适当增减衣服。体质较弱的朋友请注意防护。",
"forecast": [{
"fengxiang": "北风",
"fengli": "3-4级",
"high": "高温 14℃",
"type": "晴",
"low": "低温 3℃",
"date": "19日星期六"
},
{
"fengxiang": "无持续风向",
"fengli": "微风级",
"high": "高温 4℃",
"type": "雨夹雪",
"low": "低温 -1℃",
"date": "20日星期天"
},
{
"fengxiang": "北风",
"fengli": "3-4级",
"high": "高温 0℃",
"type": "小雪",
"low": "低温 -7℃",
"date": "21日星期一"
},
{
"fengxiang": "北风",
"fengli": "3-4级",
"high": "高温 -3℃",
"type": "晴",
"low": "低温 -9℃",
"date": "22日星期二"
},
{
"fengxiang": "无持续风向",
"fengli": "微风级",
"high": "高温 -3℃",
"type": "多云",
"low": "低温 -10℃",
"date": "23日星期三"
}],
"yesterday": {
"fl": "微风",
"fx": "无持续风向",
"high": "高温 10℃",
"type": "霾",
"low": "低温 6℃",
"date": "18日星期五"
},
"aqi": "40",
"city": "北京"
}
}
可以看出,以上返回的JSON数据很多,有当天的温度wendu、感冒描述ganmao,还有forecast数组中保存的最近5天的天气数据,以及yesterday中保存的昨日天气数据。
只需要一个接口就可获得详细的天气信息,因此,本案例选择使用该API接口。
本案例要求界面简单,尽量在一个页面中显示当前天气、最近五天的天气,同时,还要提供按城市名称查询的功能,可显示出所查询城市的天气预报信息。UI设计如图4所示。
图4 UI设计
在图4所示UI中,上方显示所查询城市的名称,右侧显示当前日期。接着以较大字号显示查询城市的温度和感冒描述。下方排着5个小卡片显示最近5天的天气信息,最下方接收用户输入要查询的城市名称,单击“查询”按钮即可查询指定城市的天气预报信息。
当刚打开该小程序时,由于用户还没有输入查询城市名称,需要设置一个默认城市名称,以方便显示初始的天气预报信息。
选择好使用的API并设计好UI界面的布局之后,就可以创建微信小程序项目,并编写界面代码和逻辑层的JavaScript代码了。
首先按以下步骤创建出项目。
(1)创建名为ch11的项目目录。
(2)启动微信小程序开发工具,在启动界面中单击“添加项目”按钮,打开如图5所示的对话框。
(3)在图5所示对话框中填写好相应的项目名称,并选择保存项目的目录,单击“添加项目”按钮即可创建好一个项目的框架。
图5 添加项目
本项目只有一个页面,因此也就不需要再添加其他页面,将index页面中已有的内容删除,然后再在index页面中编写WXML和JS代码即可。
(4)修改显示标题,打开app.json文件,修改成以下内容:
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "微天气",
"navigationBarTextStyle":"black"
}
}
根据图4所示的UI设计,打开index.wxml文件,删除该文件原有内容,输入以下wxml代码。
<view class="content">
<!--显示当天的天气信息-->
<view class="info">
<!--城市名称 当前日期-->
<view class="city">{{city}} ({{today}})</view>
<!--当天温度-->
<view class="temp">{{weather.wendu}}℃</view>
<!--感冒描述-->
<view class="weather">{{weather.ganmao}}</view>
</view>
<!--昨天的天气信息-->
<view class="yesterday">
<view class="detail"><text class="yesterday-title">昨天</text>
{{weather.yesterday.date}}</view>
<view class="detail"> {{weather.yesterday.type}} <!--天气类型,如阴、晴-->
{{weather.yesterday.fx}} <!--风向-->
{{weather.yesterday.fl}} <!--风力-->
{{weather.yesterday.low}} <!--最低温度-->
{{weather.yesterday.high}} <!--最高温度-->
</view>
</view>
<!--最近五天天气信息-->
<view class="forecast" >
<view class="next-day" wx:key="{{index}}" wx:for="{{weather.forecast}}" >
<!--日期-->
<view class="detail date">{{item.date}}</view>
<!--天气类型-->
<view class="detail">{{item.type}}</view>
<!--最高温度-->
<view class="detail">{{item.high}}</view>
<!--最低温度-->
<view class="detail">{{item.low}}</view>
<!--风向-->
<view class="detail">{{item.fengxiang}}</view>
<!--风力-->
<view class="detail">{{item.fengli}}</view>
</view>
</view>
<!--搜索-->
<view class="search-area">
<input bindinput="inputing" placeholder="请输入城市名称"
value="{{inputCity}}" />
<button type="primary" size="mini" bindtap="bindSearch">查询</button>
</view>
</view>
以上wxml代码添加了注释,每一部分的作用都在注释中进行了描述。
保存以上wxml代码之后,在开发工具左侧的预览区中并没有看到如图4中的UI效果。为了达到设计的布局效果,需要编写样式代码对wxml组件进行控制。其实,在上面的wxml代码中,已经为各组件设置了class属性,接下来只需要在index.wxss中针对每一个class编写相应的样式代码即可,具体代码如下:
.content{
height: 100%;
width:100%;
display:flex;
flex-direction:column;
font-family: 微软雅黑, 宋体;
box-sizing:border-box;
padding:20rpx 10rpx;
color: #252525;
font-size:16px;
background-color:#F2F2F8;
}
/*当天天气信息*/
.info{
margin-top:50rpx;
width:100%;
height:160px;
}
/*城市名称*/
.city{
margin: 20rpx;
border-bottom:1px solid #043567;
}
/*当天温度*/
.temp{
font-size: 120rpx;
line-height: 130rpx;
text-align: center;
padding-top:20rpx;
color:#043567;
}
/*感冒描述*/
.weather{
line-height: 22px;
margin: 10px 0;
padding: 0 10px;
}
/*昨天天气信息*/
.yesterday{
width:93%;
padding:20rpx;
margin-top:50rpx;
border-radius:10rpx;
border:1px solid #043567;
}
/*昨天的*/
.yesterday-title{
color:red;
}
/*最近五天天气信息*/
.forecast{
width: 100%;
display:flex;
margin-top:50rpx;
align-self:flex-end;
}
/*每一天的天气信息*/
.next-day{
width:20%;
height:450rpx;
text-align:center;
line-height:30px;
font-size:14px;
margin: 0 3rpx;
border:1px solid #043567;
border-radius:10rpx;
}
/*日期*/
.date{
margin-bottom:20rpx;
border-bottom:1px solid #043567;
color:#F29F39;
}
/*搜索区域*/
.search-area{
display:flex;
background: #f4f4f4;
padding: 1rem 0.5rem;
}
/*搜索区域的输入框*/
.search-area input{
width:70%;
height: 38px;
line-height: 38px;
border: 1px solid #ccc;
box-shadow: inset 0 0 10px #ccc;
color: #000;
background-color:#fff;
border-radius: 5px;
}
/*搜索区的按钮*/
.search-area button{
width: 30%;
height: 40px;
line-height: 40px;
margin-left: 5px;
}
在上面的wxss代码中,每一个class设置前都有相应的注释,可与wxml代码对应起来。
保存好index.wxss文件之后,开发工具左侧预览区可看到如图6所示的界面效果。
图6 界面效果
由于在index.js中还没有设置初始化数据,所以在图6所示界面中看不到具体的数据,从而也导致界面的效果没达到设置的要求。
接下来就编写逻辑层代码index.js,为了检查界面设计效果,首先编写初始数据,然后再逐步深入地编写其他相关业务逻辑代码。
在index.wxml中编写了很多数据,因此需要在index.js中先把这些数据进行初始化,然后在开发工具的模拟器中就可预览结果。
打开index.js文件,删除原来的内容,重新编写以下代码:
Page({
data: {
weather:{
wendu:18,
ganmao:'昼夜温差较大,较易发生感冒,请适当增减衣服。体质较弱的朋友请注意防护。',
yesterday:{
date:'17日星期四',
type:'阴',
fx:'南风',
fl:'微风级',
low:'低温 8℃',
high:'高温 16℃'
},
forecast:[
{
date:'18日星期五',
type:'阴',
high:'高温 16℃',
low:'低温 8℃',
fengxiang:'南风',
fengli:'微风级'
},{
date:'18日星期五',
type:'阴',
high:'高温 16℃',
low:'低温 8℃',
fengxiang:'南风',
fengli:'微风级'
},{
date:'18日星期五',
type:'阴',
high:'高温 16℃',
low:'低温 8℃',
fengxiang:'南风',
fengli:'微风级'
},{
date:'18日星期五',
type:'阴',
high:'高温 16℃',
low:'低温 8℃',
fengxiang:'南风',
fengli:'微风级'
},{
date:'18日星期五',
type:'阴',
high:'高温 16℃',
low:'低温 8℃',
fengxiang:'南风',
fengli:'微风级'
}
]
},
today:'2016-11-18',
city:'北京', //城市名称
inputCity:'', //输入查询的城市名称
}
})
编写好以上初始化数据之后,保存index.js,在开发工具左侧预览区域可看到如图7所示的界面效果。
图7 界面效果
以上代码很长,主要是由于模拟了5天的天气数据,实际上,在小程序运行时,应该在打开小程序之后就马上通过API获取天气数据,因此上面的初始化数据代码中,只需要用以下语句将weather初始化为一个空对象即可,而上面添加在weather中的属性数据都可以删除。
weather:{}
根据本案例的要求,当用户打开本案例之后,首先要获取用户当前所在城市的天气信息,这就需要获取用户当前所在城市的名称。要完成这个功能,需要经过几个转折。
首先,可以使用微信小程序的获取当前地理位置经纬度的API(就是wx. getLocation),通过该API即可获取用户所在位置的经纬度。
有了用户所在的经纬度,还需要查询该经纬度对应的城市名称。这可以使用百度地图的接口来实现,百度地图Geocoding API服务地址如下:
http://api.map.baidu.com/geocoder/v2/
调用该接口需要传递以下几个参数。
例如,在浏览器中输入以下地址:
http://api.map.baidu.com/geocoder/v2/?ak=ASAT5N3tnHIa4APW0SNPeXN5&location=30.572269,104.066541&output=json&pois=0
返回的JSON格式如下所示:
{
"status": 0,
"result": {
"location": {
"lng": 104.06654099999996,
"lat": 30.572268897395259
},
"formatted_address": "四川省成都市武侯区G4201(成都绕城高速)",
"business": "",
"addressComponent": {
"country": "中国",
"country_code": 0,
"province": "四川省",
"city": "成都市",
"district": "武侯区",
"adcode": "510107",
"street": "G4201(成都绕城高速)",
"street_number": "",
"direction": "",
"distance": ""
},
"pois": [],
"poiRegions": [],
"sematic_description": "**中心w6区西南108米",
"cityCode": 75
}
}
在以上JSON数据中,通过result.addressComponent.city可获取传入经纬度对应的城市名称。因此,在本案例中可通过这种方式获取用户当前所在城市的名称。
根据以上分析,在index.js的onLoad事件处理函数中编写如下所示代码:
var util = require('../../utils/util.js');
Page({
data: {
……
},
onLoad: function (options) {
this.setData({
today:util.formatTime(new Date()).split(' ')[0] //更新当前日期
});
var self = this;
wx.getLocation({
type: 'wgs84',
success: function (res) {
wx.request({
url:'http://api.map.baidu.com/geocoder/v2/' +
'?ak=ASAT5N3tnHIa4APW0SNPeXN5&location='+
res.latitude+',' + res.longitude + '&output=json&pois=0',
data: {},
header: {
'Content-Type': 'application/json'
},
success: function (res) {
var city = res.data.result.addressComponent.city.replace('市','');//城市名称
self.searchWeather(city); //查询指定城市的天气信息
}
})
}
})
},
})
以上代码中,第1行使用require导入工具方法,用来格式化日期。
获取了城市名称,接下来就可使用以下接口获取指定城市名称的天气预报信息:
http://wthrcdn.etouch.cn/weather_mini?city=城市名称
在上面的接口中,城市名称中不包含“市”这个字,如“成都市”只需要传入“成都”。
在本节前面介绍该接口时,只查看了接口执行成功后返回的JSON数据,如果传入的城市名称有误,则返回如下所示JSON数据:
{
"desc": "invilad-citykey",
"status": 1002
}
在程序中可通过status判断数据查询是否成功。
由于根据城市名称查询天气预报信息的代码需要重复调用,因此,单独编写成一个函数,方便在查询时调用。
//根据城市名称查询天气预报信息
searchWeather:function(cityName){
var self = this;
wx.request({
//天气预报查询接口
url: 'http://wthrcdn.etouch.cn/weather_mini?city='+cityName,
data: {},
header: {
'Content-Type': 'application/json'
},
success: function (res) {
if(res.data.status == 1002) //无此城市
{
//显示错误信息
wx.showModal({
title: '提示',
content: '输入的城市名称有误,请重新输入!',
showCancel:false,
success: function(res) {
self.setData({inputCity:''});
}
})
}else{
var weather = res.data.data; //获取天气数据
for(var i=0;i<weather.forecast.length;i++)
{
var d = weather.forecast[i].date;
//处理日期信息,添加空格
weather.forecast[i].date = ' ' + d.replace('星期',' 星期');
}
self.setData({
city:cityName, //更新显示城市名称
weather:weather, //更新天气信息
inputCity:'' //清空查询输入框
})
}
}
})
}
在上面代码中,获取的date中保存的是“19日星期六”这种格式的字符串,为了使日期和星期分别显示在两行中,这里使用了一种小技巧,就是在日期字符串中添加了2个全角状态的空格,这样在显示这个字符串时自动断行。
编写好以上这些代码之后,保存,在开发工具左侧可看到已经获取当前的天气数据,而不是前面初始化的数据了,如图8所示。
图8 用户所在地天气预报
这样,本案例的主要代码就算编写完成了。不过,还只能显示用户当前所在地的天气信息,如果要查看其他城市的天气,还需要继续编写相应的查询代码。
查询代码的编写很简单,只需要获取用户输入的城市名称,然后传入searchWeather函数即可。具体的代码如下:
//输入事件
inputing:function(e){
this.setData({inputCity:e.detail.value});
},
//搜索按钮
bindSearch:function(){
this.searchWeather(this.data.inputCity);
}
保存以上代码之后,在开发工具左侧模拟器中输入查询的城市名称,如输入“三亚”,单击“查询”按钮,界面中即可显示“三亚”的天气信息,如图9所示。
图9 查询城市天气
如果在下方输入框输入一个不存在的城市名称,将显示如图11-10所示的提示信息。
图10 城市名称错误的提示