微天气 - 开发一个完整的微信小程序

微天气

这次咱们一起开发一个天气预报小程序,之所以选这个类型,有两个原因。 一是天气类的小程序整体复杂度比较低,适合我们说明问题。 另外,这种类型的应用也更加适合微信小程序生态的场景,我的理解这个生态不适宜开发过于复杂的应用。 所以选了则个类型。咱们首先来看看最终效果,有个感官的认识:

这个是在我的调试器上面的最终运行效果,根据你当前的位置显示当前以及未来一周的天气情况。 对于小程序的使用场景,个人觉得比较适合。

准备工作

先给大家看了运行效果,建立一个感官认识。那么接下来,我们就可以开始了。首先在微信开发者工具里建立一个项目:

接下来,项目创建窗口有一点说明下, 在 AppID 条目中, 如果你已经有了内测账号,就填写你的 AppID, 如果你还没有内测账号,点击旁边的无AppID就可以了(相信大多数同学是没有内测账号的,所以可以使用这个方法继续在本地调试)。

关于小程序的基本流程,以及项目结构等,可以参看咱们之前的文章 不需内测账号,带你体验微信小程序完整开发过程。 基础部分咱们这里就不多赘述。

项目结构

整个项目的文件结构如下:

大家可以看到,有一个 index 目录里面是主页。 app.js 是程序主入口, utils.js 是咱们的工具脚本,用于读取天气数据。 还有一个 bg.jpg 的图片文件,是小程序的背景图。

这个项目的所有文件都在这里了,是不是挺简单的? 项目的完整代大家可以在咱们的 Github 主页上查看 https://github.com/swiftcafex/wechat-weather

基本结构介绍完了, 接下来咱们可以开始 Coding 了。 对于这个天气程序来说, 首先要处理的一个事情就是天气数据的获取了。 那咱们就一步一步的来做。

首先,我们需要获取当前的地理位置, 微信给我们提供了相应的接口, 我们在 util.js 中可以定义这样一个方法:

function getLocation(callback) {

wx.getLocation({

success: function(res) {

callback(true, res.latitude, res.longitude);

},
fail: function() {

callback(false);

}

})

}

wx.getLocation 方法给我们返回一个我们当前位置的经纬度信息。 如果成功,我们将信息传回给 callback, 如果失败我们给 callback 传回 false。 注意,失败的情况在实际开发中是需要注意处理的。比如,如果一些用户没有开启定位权限,不处理失败的话,就有可能产生预期之外的情况了。

获取到当前位置之后,我们还要获取什么呢? 天气数据。 相关的 API 很多, 我们这个小程序用的是 darksky.net 提供的天气 API。 它提供了一个很简单的 API 接口:

function getWeatherByLocation(latitude, longitude, callback) {

var apiKey = "你自己的Key";
var apiURL = "https://api.darksky.net/forecast/" + apiKey + "/" + latitude + "," + longitude + "?lang=zh&units=ca";

wx.request({
url: apiURL,
success: function(res){

var weatherData = parseWeatherData(res.data);
getCityName(latitude, longitude, function(city){
weatherData.city = city;
callback(weatherData);
});

}
});

}

getWeatherByLocation 这个方法依然写在 util.js 里面,它的逻辑也很简单,拼接出 darksky 的 API 的 URL,然后调用 wx.request 请求网络数据。 因为我们不需要用到 API 返回的所有数据, 只需要获得当天的天气,以及未来 7 天的预报即可。 所以这里还使用 parseWeatherData 方法取得我们需要的数据并重组成新的结果。 这个方法的定义如下:

function parseWeatherData(data) {

var weather = {};
weather["current"] = data.currently;
weather["daily"] = data.daily;

return weather;

}

从上面的代码不难看出,我们只取得了原始结果集的 currently 和 daily 数据,然后重新返回。 为什么我们要这样取得部分数据呢,主要是因为这个接口的其他数据我们并不需要,所以就没必要再传给应用层了。 原始数据的格式给大家贴一下:

优化数据格式

大家可能注意到了,这个 API 给我们返回的数据中,有些数据的格式我们还需要继续处理一下。 比如 time 是用时间戳的形式给我们返回的,但我们需要将时间显示在 UI 上, 所以我们就需要进行一下格式转换。 另外 temperature 字段的格式也不是我们需要的。温度数据我们不需要显示到小数点之后,取整数就可以。

定义几个格式化数据的方法:

//将时间戳格式化为日期
function formatDate(timestamp) {

var date = new Date(timestamp * 1000);
return date.getMonth()+1 + "月" + date.getDate() + "日 " + formatWeekday(timestamp);

}

//将时间戳格式化为时间
function formatTime(timestamp) {

var date = new Date(timestamp * 1000);
return date.getHours() + ":" + date.getMinutes();

}

//中文形式的每周日期
function formatWeekday(timestamp) {

var date = new Date(timestamp * 1000);
var weekday = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
var index = date.getDay();

return weekday[index];


}

这三个方法都是对日期进行格式化输出。具体功能代码里的注释已经说明了,不多赘述。 最后,我们把前面所有的方法整合起来,组成给应用层的接口:

//加载天气数据
function requestWeatherData(cb) {

getLocation(function(success, latitude, longitude){

//如果 GPS 信息获取不成功, 设置一个默认坐标
if(success == false) {

latitude = 39.90403;
longitude = 116.407526;

}

//请求天气数据 API
getWeatherByLocation(latitude, longitude, function(weatherData){

cb(weatherData);

});

});

}

请求原始数据,这里调用了 getLocation 请求当前位置, 在回调里面判断返回结果是否获取位置成功,如果不成功,设置一个默认位置。 这个判断在实际的产品中还是比较有用的。 位置获取不成功的情况还是比较多的。比如用户没有开启定位权限。

紧接着,在里面又调用了 getWeatherByLocation 方法获取天气数据,然后将原始的天气数据返回。

原始数据读取成功后, 我们再封装一层,将原始数据进行加工:

function loadWeatherData(callback) {

requestWeatherData(function(data){

//对原始数据做一些修整, 然后输出给前端
var weatherData = {};
weatherData = data;
weatherData.current.formattedDate = formatDate(data.current.time);
weatherData.current.formattedTime = formatTime(data.current.time);
weatherData.current.temperature = parseInt(weatherData.current.temperature);

var wantedDaily = [];
for(var i = 1;i < weatherData.daily.data.length;i++) {

var wantedDailyItem = weatherData.daily.data[i];
var time = weatherData.daily.data[i].time;
wantedDailyItem["weekday"] = formatWeekday(time);
wantedDailyItem["temperatureMin"] = parseInt(weatherData.daily.data[i]["temperatureMin"])
wantedDailyItem["temperatureMax"] = parseInt(weatherData.daily.data[i]["temperatureMax"])

wantedDaily.push(wantedDailyItem);

}

weatherData.daily.data = wantedDaily;
callback(weatherData);

});

}

这是最终输出给应用层的方法,它里面用了咱们刚才定义的几个数据格式化方法将返回的原始天气数据加工了一下。 最终传递给回调方法。

最后我们将这个方法暴露给应用层:

module.exports = {

loadWeatherData: loadWeatherData

}

这个语法和 nodejs 比较相似。 到此为止,咱们这个小程序的数据处理逻辑部分就开发完成了。 大家可以稍微消化一下, 下一篇会和大家一起处理应用层的逻辑。 如果你想查看完整的代码, 也可以进入 Github 主页下载: https://github.com/swiftcafex/wechat-weather

 

微天气应用层

在上一篇文章中,咱们把微天气的数据层的逻辑搭建完成了。这次我们来构建这个小程序的应用层。上一篇文章的内容可以参考这里:

微天气 - 开发一个完整的微信小程序(上)

数据层开发完成,接下来我们就可以专注应用层的逻辑了。 我们这个小程序不需要修改 app.js 只保留它的默认代码即可:

//app.js

App({
onLaunch: function () {

},
globalData:{
userInfo:null
}
})

主要的应用层逻辑都在 index.js 这个页面上:

//index.js
//获取应用实例

var util = require('../../util.js')

Page({

data: {
weather: {}
},
onLoad: function () {

var that = this;

util.loadWeatherData(function(data){

that.setData({
weather: data
});

});

}

})

大体看一下, 也并不复杂。 首先使用 require 语句导入我们上一篇文章中定义的 util.js 文件。 这里面提供了获取天气数据的整个逻辑。

然后 Page 对象中, data 数据层定义了天气数据的结构:

data: {
weather: {}
}

在 onLoad 方法中, 使用 util 中的 loadWeatherData 方法获取天气数据并设置到 UI 上:

onLoad: function () {

var that = this;

util.loadWeatherData(function(data){

that.setData({
weather: data
});

});

}

这个逻辑也不难理解,获取到数据后, 使用 setData 方法将它设置到数据层中。 注意,一定要用 setData 方法。 不能直接用这种属性赋值形式:

that.data.weather = data

这样虽然也能设置底层数据,但它不能更新 UI 层的显示。 这也是微信数据绑定机制的一个原理。 所以大家在操作数据绑定的时候,一定要注意这一点, 否则就会容易造成很麻烦的调试问题。

到此为止, 小程序的应用逻辑部分就完成了。 怎么样,很简单吧。 对于应用层这块的逻辑,主要注意数据绑定和声明周期相关的内容即可。这两个地方比较容易产生非预期的结果。 其他地方和我们开发其他程序基本差不多。 关于应用层逻辑,咱们就聊到这里, 下篇再和大家聊聊 UI 层相关的内容。这样我们就可以对小程序的整个开发过程有一个了解了。

 

前面两篇文章中, 我们分别讨论了微天气小程序的数据层,以及应用层的相关思路。 这次咱们继续进行 UI 层的开发。 如果需要了解前面的内容,大家还可以参考这两篇文章:

微天气 - 开发一个完整的微信小程序(上)

微天气 - 开发一个完整的微信小程序(中)

那么咱们继续, 首先咱们来看一下 index.wxml, 这个页面中定义了 index 页面的 UI:

<view class="container">
<view class="top">
<view>{{weather.city}}</view>
<view>{{weather.current.formattedDate}}</view>
<view>{{weather.current.formattedTime}} 更新</view>
</view>
<view class="topRegion">
<view id="temperature" >{{weather.current.temperature}}℃</view>
<view id="summary" >{{weather.current.summary}}</view>
</view>
<view class="summary" >
<view>一周天气预报</view>
<view style="margin-top:20rpx">{{weather.daily.summary}}</view>
</view>
<view class="daily" >
<view class="daily_item" wx:for="{{weather.daily.data}}">
<view class="daily_weekday" >{{item.weekday}}</view>
<view class="daily_temperature" >{{item.temperatureMin}}-{{item.temperatureMax}}℃ </view>
<view class="daily_summary" >{{item.summary}}</view>
</view>
</view>
</view>
-->

首页的所有 UI 内容就都在这里了, 大家是否还记得咱们这个小程序主界面的样子? 贴出来再给大家回顾一下:

这个界面就是上面那段代码生成的了。 接下来咱们逐一分解。 把上面的完整代码简化一下,咱们先来看看整个 UI 的结构:

<view class="container">
<view class="top">
</view>
<view class="topRegion">
</view>
<view class="summary" >
</view>
<view class="daily" >
</view>
</view>
</view>
-->

最外层是一个 class 为 container 的 View, 它的里面放了 4 个子 View, 分别为 top, topRegion,summary 和 daily。

top 区域是我们最顶部的地方

来看看 top 的完整定义:

<view class="top">
<view>{{weather.city}}</view>
<view>{{weather.current.formattedDate}}</view>
<view>{{weather.current.formattedTime}} 更新</view>
</view>
-->

里面的 3 个子视图分别对应了要显示的几个数据条目, 并且用一对大括号来引用我们 index.js 中定义的 data 数据中的内容。 关于数据绑定的基本知识咱们在之前的文章中已经介绍过, 如果对数据绑定不熟悉的话还可以参看之前的内容~

然后接下来就是 topRegion, 这个区域也很简单,显示我们当前地区的温度以及天气情况:

<!--
<view class="topRegion">
<view id="temperature" >{{weather.current.temperature}}℃</view>
<view id="summary" >{{weather.current.summary}}</view>
</view>
-->

还是简单的数据绑定, 体现在界面上就是这个区域:

summary 区域的逻辑和前面两个分别不大, 就不多说了。 最后再来看一下 daily 部分, 这里面用到了一个循环语法:

<view class="daily" >
<view class="daily_item" wx:for="{{weather.daily.data}}">
<view class="daily_weekday" >{{item.weekday}}</view>
<view class="daily_temperature" >{{item.temperatureMin}}-{{item.temperatureMax}}℃ </view>
<view class="daily_summary" >{{item.summary}}</view>
</view>
</view>
-->

它的第一个子视图使用了 wx:for 这个语法。 这个标记相当于对传入它的属性进行一个循环遍历, 也就是 {{weather.daily.data}}。 然后这个标签内部的子标签会根据集合的数量重复出现,如果要引用每次遍历到的元素, 可以使用 item, 比如我们这里的 {{item.weekday}} 和 {{item.summary}} 等。

这样,我们最终在界面看到的效果是一个循环遍历后的结果:

到此为止, index.wxml 的内容咱们就都介绍完了。 但是单纯的只有这些还不能构成完整的 UI 界面。 还需要最后一个东西, 那就是 wxss,也就是样式表。 那么继续来看几个例子:

.container {
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
background-image: url(images/bg.jpg);
background-size: 100%;
padding: 20rpx;
}

这个是最外层 container 的样式,它除了可以使用传统的 css 样式, 还可以使用微信特有的一些样式, 比如 display: flex 和 flex-direction: column。 还要注意我们这里用到了一个新的单位 rpx。 这个也是微信自有的特性 - responsive pixel。 它相当于一个自适应尺寸,所有的屏幕宽度都是 750rpx, 我们只需要记得这个特性即可, 其他的微信小程序会根据具体的手机尺寸自行计算相对尺寸。

这里我就捡两个重要的特点和大家介绍一下。 这个小程序完整的样式表比较繁琐。而且都是相似的内容, 就不跟大家过多讲解。 如果大家需要了解完整的内容, 还是可以到咱们的 Github 主页上面下载完整的项目 https://github.com/swiftcafex/wechat-weather

结尾

到此为止,用了三篇文章跟大家介绍了一个小程序从构建到开发的完整过过程。 这个天气小程序逻辑并不复杂,但通过它大家应该可以对整个开发的过程有一个亲身的了解。也是希望它能起到一个抛砖引玉的作用,大家如果有好的想法 也欢迎大家在 Github 上面 Fork 这个项目,一起来完善它。

转:http://swiftcafe.io/2016/10/03/wx-weather-app/

展开阅读全文

没有更多推荐了,返回首页