两天撸一个天气应用微信小程序

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

两天撸⼀个天⽓应⽤微信⼩程序
更新说明:
I、⽓象数据由修改为了,需要注册账号获取key;
II、d0e51c8版本之后为版本,若未开通云开发功能,为不影响⼩程序正常运⾏,可以将版本号回退到git reset d0e51c8 --hard,或,将云开发相关代码注释掉。

具体可查看。

简介
这是⼀个完整的已经线上运⾏的天⽓应⽤⼩程序,,可随意 star。

也可以扫描下⽅的⼩程序码直接体验。

顺便推荐另⼀个开源⼩程序,。

新版⾸页(可选择内置背景)
效果图:
说明
鸣谢:pure 天⽓ APP:⾸页样式借鉴了 pure天⽓ APP。

如侵删。

数据来源
地理编码、天⽓数据均来⾃。

个⼈开发完全免费,有对应的⼩程序 sdk,加⼊即可,但是返回的天⽓数据较少。

运⾏前准备
,获取 appid
注册开发者,创建应⽤,获取 ak(其他配置⾃⾏查看)
在 app.js 中替换 globalData 中的 ak 为⾃⼰的 ak
Run
利益相关

天⽓数据获取
因为只是⼀个个⼈版DEMO(完整版),开发前就决定选择免费的天⽓数据(个⼈开发免费),懒得去寻找其他的天⽓数据,懒得去注册账号,就直接选择了的天⽓数据,正好也提供了⼩程序对应的 sdk,但是可能相⽐于其他的天⽓ API,百度返回的数据偏少:当天 pm2.5、当天和未来三天数据、当天⽣活指数,其他的就没有了。

但是对于⼀款简单的天⽓应⽤⼩程序来说也够了。

地理编码
获取天⽓数据默认返回当前城市的天⽓数据,如果要获取其他的城市的天⽓数据,需要传⼊经纬度。

为了获取其他城市的经纬度,这⾥使⽤的地图的地理编码接⼝,输⼊城市名,输出经纬度,然后调⽤获取天⽓数据 API 即可。

具体实现
该应⽤只有五个个页⾯:⾸页、城市选择页、设置页、关于页、系统信息页(展⽰页)。

如下:
⾸页
⾸页最终的显⽰效果是这个样⼦:
从上到下依次是:其他城市天⽓搜索、当前城市数据展⽰、当天和未来三天天⽓数据展⽰、当天⽣活指数展⽰、footer。

下拉刷新会刷新当前地区的天⽓数据。

其中,顶部城市天⽓搜索和⽣活指数可以在设置中隐藏。

屏幕右下⾓是⼀个可以移动的悬浮球(⽚??)菜单,点击后会弹出城市选择、设置、关于页⾯的⼊⼝。

背景⾊默认是#40a7e7纯⾊,可在设置中更换背景图,未来三天天⽓预报和⽣活指数分别添加了透明的⿊⾊背景。

设计稿?没有的,纯⾁眼调试,直到⾃⼰看着舒服。

主页⾯
先定义⼀个⽅法获取当前地区的天⽓数据:
init(params) {
let that = this
let BMap = new bmap.BMapWX({
ak: globalData.ak,
})
BMap.weather({
location: params.location,
fail: that.fail,
success: that.success,
})
},
ak请替换为⾃⼰的ak,因为需要获取⽤户的地理位置,所以在fail的回调中需要处理⽤户拒绝获取地理位置的逻辑,这⾥处理为:提⽰打开地理位置授权,3000ms后
wx.openSetting()跳转到⼩程序设置页,如下:
fail (res) {
wx.stopPullDownRefresh()
let errMsg = res.errMsg || ''
// 拒绝授权地理位置权限
if (errMsg.indexOf('deny') !== -1 || errMsg.indexOf('denied') !== -1) {
wx.showToast({
title: '需要开启地理位置权限',
icon: 'none',
duration: 3000,
success (res) {
let timer = setTimeout(() => {
clearTimeout(timer)
wx.openSetting({})
}, 3000)
},
})
} else {
wx.showToast({
title: '⽹络不给⼒,请稍后再试',
icon: 'none',
})
}
},
获取到⽤户的地理位置后,执⾏success:
success (data) {
wx.stopPullDownRefresh()
let now = new Date()
// 存下来源数据
data.updateTime = now.getTime()
data.updateTimeFormat = utils.formatDate(now, "MM-dd hh:mm")
let results = data.originalData.results[0] || {}
data.pm = this.calcPM(results['pm25'])
// 当天实时温度
data.temperature = `${results.weather_data[0].date.match(/\d+/g)[2]}`
wx.setStorage({
key: 'cityDatas',
data: data,
})
this.setData({
cityDatas: data,
})
},
看⼀下返回的天⽓数据格式:
{
"error": 0,
"status": "success",
"date": "2018-06-29",
"results": [
{
"currentCity": "北京市",
"pm25": "55",
"index": [
{
"des": "天⽓炎热,建议着短衫、短裙、短裤、薄型T恤衫等清凉夏季服装。

",
"zs": "炎热",
"tipt": "穿⾐指数",
"title": "穿⾐"
},
{
"des": "较适宜洗车,未来⼀天⽆⾬,风⼒较⼩,擦洗⼀新的汽车⾄少能保持⼀天。

",
"zs": "较适宜",
"tipt": "洗车指数",
"title": "洗车"
},
{
"des": "各项⽓象条件适宜,发⽣感冒机率较低。

但请避免长期处于空调房间中,以防感冒。

",
"zs": "少发",
"tipt": "感冒指数",
"title": "感冒"
},
{
"des": "天⽓较好,⽆⾬⽔困扰,但考虑⽓温很⾼,请注意适当减少运动时间并降低运动强度,运动后及时补充⽔分。

", "zs": "较不宜",
"tipt": "运动指数",
"title": "运动"
},
{
"des": "属中等强度紫外线辐射天⽓,外出时建议涂擦SPF⾼于15、PA+的防晒护肤品,戴帽⼦、太阳镜。

",
"zs": "中等",
"tipt": "紫外线强度指数",
"title": "紫外线强度"
}
],
"weather_data": [
{
"date": "周五 06⽉29⽇ (实时:34℃)",
"dayPictureUrl": "/images/weather/day/duoyun.png",
"nightPictureUrl": "/images/weather/night/qing.png",
"weather": "多云转晴",
"wind": "东南风微风",
"temperature": "38 ~ 25℃"
},
{
"date": "周六",
"dayPictureUrl": "/images/weather/day/duoyun.png",
"nightPictureUrl": "/images/weather/night/duoyun.png",
"weather": "多云",
"wind": "东南风微风",
"temperature": "36 ~ 23℃"
},
{
"date": "周⽇",
"dayPictureUrl": "/images/weather/day/qing.png",
"nightPictureUrl": "/images/weather/night/qing.png",
"weather": "晴",
"wind": "东南风微风",
"temperature": "35 ~ 23℃"
},
{
"date": "周⼀",
"dayPictureUrl": "/images/weather/day/qing.png",
"nightPictureUrl": "/images/weather/night/duoyun.png",
"weather": "晴转多云",
"wind": "南风微风",
"temperature": "35 ~ 25℃"
}
]
}
]
}
success⾥缓存了最新⼀次获取的天⽓数据+更新的时间cityDatas,⼩程序的模板⾥⽆法使⽤⽅法,所以数据需要在js⾥⾯先格式化。

calcPM⽤来计算当前 pm2.5 的质量,返回“优
良差”类似字样,范围标准可⾃⾏搜索。

当天的实时温度并没有给出独⽴的字段,⽽是混在了wearther_data[0]的data字段⾥:"date": "周五 06⽉29⽇ (实时:34℃)",需要⾃⾏提取。

返回
的天⽓ icon 和⾊调不搭,就没有使⽤。

其他的数据按照按照我们要显⽰的格式直接填充即可。

城市天⽓搜索
获取天⽓数据传参为经纬度,所以搜索城市天⽓时,需先将城市转换为对应的经纬度,然后调⽤获取天⽓数据 API 即可。

获取经纬度的 API 为:
返回的数据格式为:
{
"status":0,
"result":{
"location":{
"lng":117.21081309155257,
"lat":39.143929903310074
},
"precise":0,
"confidence":12,
"level":"城市"
}
}
然后直接调⽤获取天⽓ API 即可。

具体代码如下:
geocoder (address, success) {
let that = this
wx.request({
url: getApp().setGeocoderUrl(address),
success (res) {
let data = res.data || {}
if (!data.status) {
let location = (data.result || {}).location || {}
// location = {lng, lat}
success && success(location)
} else {
wx.showToast({
title: data.msg || '⽹络不给⼒,请稍后再试',
icon: 'none',
})
}
},
fail (res) {
wx.showToast({
title: res.errMsg || '⽹络不给⼒,请稍后再试',
icon: 'none',
})
},
complete () {
that.setData({
searchText: '',
})
},
})
},
search (val) {
// 动画
if (val === '520' || val === '521') {
this.setData({
searchText: '',
})
this.dance()
return
}
wx.pageScrollTo({
scrollTop: 0,
duration: 300,
})
if (val) {
let that = this
this.geocoder(val, (loc) => {
that.init({
location: `${loc.lng},${t}`
})
})
}
},
搜索动画彩蛋
在搜索框⾥搜索520或521,会出现从顶部下⼩⼼⼼的动画,如下:
这⾥实现⽐较简单。

创建了⼀个heartbeat的组件。

wxml结构是遍历数组,创建多个⼤⼩、位置随机的图⽚:
<image wx:for='{{arr}}' wx:key='{{index}}' animation='{{animations[index]}}' class='heart' style='left:{{lefts[index]}}px;top:{{tops[index]}}px;width:{{widths[index]}}rpx;height:{{widths[index]}}rpx;' src='/img/heartbeat.png'></image>然后使⽤的是⼩程序提供的wx.createAnimation,动画的使⽤⽐较简单,创建动画,然后赋予animation属性即可,⽐较简单,但是也有局限性,⽐如,没有直接的动画结束后的回
调,但是可以使⽤setTimeout来实现等。

这⾥会⽤到可⽤窗⼝宽⾼,因为多处⽤到了该参数,所以在app.js⾥⾯异步获取了先。

动画代码如下:
dance (callback) {
let windowWidth = this.data.windowWidth
let windowHeight = this.data.windowHeight
let duration = this.data.duration
let animations = []
let widths = []
let obj = {}
for (let i = 0; i < this.data.arr.length; i++) {
lefts.push(Math.random() * windowWidth)
tops.push(-140)
widths.push(Math.random() * 50 + 40)
let animation = wx.createAnimation({
duration: Math.random() * (duration - 1000) + 1000
})
animation.top(windowHeight).left(Math.random() * windowWidth).rotate(Math.random() * 960).step()
animations.push(animation.export())
}
this.setData({
lefts,
tops,
widths,
})
let that = this
let timer = setTimeout(() => {
that.setData({
animations,
})
clearTimeout(timer)
}, 200)
let end = setTimeout(() => {
callback && callback()
clearTimeout(end)
}, duration)
},
},
⾸页搜索特定关键词后,调⽤组件dance⽅法即触发⼩⼼⼼动画。

悬浮球菜单
屏幕右下⾓的悬浮球提供了三个页⾯的⼊⼝:城市选择页、设置页、关于页。

菜单弹出、收回会有动画。

这⾥的动画分为弹出和收起,两者写起来基本上⼀样的,只是动画的参数不⼀样。

这⾥贴出弹出的动画:
// wxml
<!-- 悬浮菜单 -->
<view class='menus'>
<image src="/img/location.png" animation="{{animationOne}}" class="menu" bindtap="menuOne" style='top:{{pos.top}}px;left:{{pos.left}}px;'></image>
<image src="/img/setting.png" animation="{{animationTwo}}" class="menu" bindtap="menuTwo" style='top:{{pos.top}}px;left:{{pos.left}}px;'></image>
<image src="/img/info.png" animation="{{animationThree}}" class="menu" bindtap="menuThree" style='top:{{pos.top}}px;left:{{pos.left}}px;'></image>
<image src="/img/menu.png" animation="{{animationMain}}" class="menu main" bindtap="menuMain" catchtouchmove='menuMainMove' style='top:{{pos.top}}px;left:{{pos.left}}px;'></image>
</view>
// js
popp() {
let animationMain = wx.createAnimation({
duration: 200,
timingFunction: 'ease-out'
})
let animationOne = wx.createAnimation({
duration: 200,
timingFunction: 'ease-out'
})
let animationTwo = wx.createAnimation({
duration: 200,
timingFunction: 'ease-out'
})
let animationThree = wx.createAnimation({
duration: 200,
timingFunction: 'ease-out'
})
animationMain.rotateZ(180).step()
animationOne.translate(-50, -60).rotateZ(360).opacity(1).step()
animationTwo.translate(-90, 0).rotateZ(360).opacity(1).step()
animationThree.translate(-50, 60).rotateZ(360).opacity(1).step()
this.setData({
animationMain: animationMain.export(),
animationOne: animationOne.export(),
animationTwo: animationTwo.export(),
animationThree: animationThree.export(),
})
},
悬浮菜单是可以在屏幕上随意滑动的,⽅法也很简单,监听touchmove事件即可,因为菜单展开⽅向是在左边,所以悬浮菜单能往左边移动的最远距离要有⼀段间隔,否则展开的菜单就进⼊左边屏幕了,移动到上⽅同样逻辑(后期可以改成菜单展开⽅向随移动⽽改变,⽽不是⼀味在左边展开)。

代码如下:
menuMainMove (e) {
// 如果已经弹出来了,需要先收回去,否则会受 top、left 会影响
if (this.data.hasPopped) {
this.takeback()
this.setData({
hasPopped: false,
})
}
let windowWidth = SYSTEMINFO.windowWidth
let windowHeight = SYSTEMINFO.windowHeight
let touches = e.touches[0]
let clientX = touches.clientX
let clientY = touches.clientY
// 边界判断
if (clientX > windowWidth - 40) {
clientX = windowWidth - 40
}
if (clientX <= 90) {
clientX = 90
}
if (clientY > windowHeight - 40 - 60) {
clientY = windowHeight - 40 - 60
}
}
let pos = {
left: clientX,
top: clientY,
}
this.setData({
pos,
})
},
⾄于⼀些样式、逻辑上的细节,这⾥不再赘述,具体可。

城市选择页
城市选择页⾯就是⼀个城市列表,如下:
点击相应的城市,跳转到⾸页获取所选城市的天⽓数据。

这⾥的城市数据是这样的格式⽆序的列表:
{ "letter": "B", "name": "北京市" }
因为需要按照字母排列进⾏排序,所以需要先排序再遍历(城市数据是之前⽤过的数据,没有排序就直接粘过来了)。

代码如下:
// 按照字母顺序⽣成需要的数据格式
getSortedAreaObj(areas) {
// let areas = staticData.areas
areas = areas.sort((a, b) => {
if (a.letter > b.letter) {
return 1
}
if (a.letter < b.letter) {
return -1
}
return 0
})
let obj = {}
for (let i = 0, len = areas.length; i < len; i++) {
let item = areas[i]
delete item.districts
let letter = item.letter
if (!obj[letter]) {
obj[letter] = []
}
obj[letter].push(item)
}
// 返回⼀个对象,直接⽤ wx:for 来遍历对象,index 为 key,item 为 value,item 是⼀个数组
return obj
},
点击城市后,需要通知⾸页“我已经切换城市了,⿇烦获取下这个城市的数据谢谢”,这⾥使⽤的是使⽤getCurrentPages获取页⾯堆栈,修改⾸页数据的⽅式。

代码如下:
choose(e) {
let item = e.currentTarget.dataset.item
let name =
let pages = getCurrentPages()
let len = pages.length
let indexPage = pages[len - 2]
indexPage.setData({
// 是否切换了城市
cityChanged: true,
// 需要查询的城市
searchCity: name,
})
wx.navigateBack({})
},
关于页
关于页是⼀个展⽰页,没有多少交互,使⽤到的 API 只有复制到剪切板wx.setClipboardData。

“微信快速联系”使⽤的是⼩程序提供的联系客服的⽅式<button open-type="contact"
class='btn'></button>,将button绝对定位隐藏到点击区域的下⽅即可。

有精⼒的话,可以⾃⼰搭建服务,将⼩程序的消息 push 到⾃⼰的服务上去。

设置页
设置页的功能看着有点多,其实并不多,只是⼀堆 API 的调⽤。

这个页⾯分了⾃定义、检查更新、⼩⼯具、清除数据三个部分。

各个设置参数保存在storage中。

⼀个⼀个来说。

1. ⾃定义
⾃定义⾸页背景
⾃定义背景是将选取的图⽚(wx.chooseImage)保存(wx.saveFile)到本地,然后⾸页获取(wx.getSavedFileList)保存的图⽚,在⾸页展⽰出来即可。

长按删除,则是获取
(wx.getSavedFileList)保存的图⽚,然后wx.removeSavedFile掉即可。

现在设置的是本地只保存⼀张图⽚,所以重新设置其他背景时,会删除上⼀张背景图,然后重新保存新背景图。

实现如下:
defaultBcg () {
this.removeBcg(() => {
wx.showToast({
title: '恢复默认背景',
duration: 1500,
})
})
},
removeBcg (callback) {
wx.getSavedFileList({
success: function (res) {
let fileList = res.fileList
let len = fileList.length
if (len > 0) {
for (let i = 0; i < len; i++)
(function (path) {
wx.removeSavedFile({
complete: function (res) {
if (i === len - 1) {
callback && callback()
}
}
})
})(fileList[i].filePath)
} else {
callback && callback()
}
},
fail: function () {
wx.showToast({
title: '出错了,请稍后再试',
icon: 'none',
})
},
})
},
customBcg () {
let that = this
wx.chooseImage({
success: function (res) {
that.removeBcg(() => {
wx.saveFile({
tempFilePath: res.tempFilePaths[0],
success: function (res) {
wx.navigateBack({})
},
})
})
},
fail: function (res) {
let errMsg = res.errMsg
// 如果是取消操作,不提⽰
if (errMsg.indexOf('cancel') === -1) {
wx.showToast({
title: '发⽣错误,请稍后再试',
icon: 'none',
})
}
},
})
},
打开顶部城市天⽓快捷搜索
该操作只是将⾸页的顶部搜索wx:if掉⽽已。

switch组件的样式可以通过修改默认的类来修改,调⼀个⾃⼰满意的即可:
.wx-switch-input{width:84rpx !important;height:43rpx !important;}
.wx-switch-input::before{width:82rpx !important;height: 38rpx !important;}
.wx-switch-input::after{width: 38rpx !important;height: 38rpx !important;}
显⽰⽣活指数信息
同样wx:if掉。

检查更新
检查更新默认关闭。

⼩程序的更新是在冷启动时去检查,如果有新版本会异步下载,再次冷启动时会加载新版本。

这⾥使⽤wx.getUpdateManager,因为该 API 基础库⽀持最低版本是 1.9.90,基础库版本低的会提⽰不⽀持,显⽰的⽂案也会相应修改。

⼩⼯具
1)NFC
使⽤wx.getHCEState。

2)屏幕亮度
获取屏幕亮度、设置屏幕亮度、保持常亮使⽤的 API 分别是wx.getScreenBrightness、wx.setScreenBrightness、wx.setKeepScreenOn。

完整实现可。

3)系统信息
系统信息会跳转到新页⾯。

清除数据
1)⾸页悬浮球复位
⾸页悬浮球的位置信息是保存本地的变量pos,复位位置,清除pos即可。

2)恢复初始化设置
设置信息是保存本地的变量setting,复位位置,清除setting即可。

3)清除所有本地数据
wx.clearStorage即可。

Tip: 恢复初始化设置、清除所有本地数据并没有删除设置的背景图(如果有设置的话),这个后续可以加上。

其他
其他代码细节,不再赘述,具体可。

更新⽇志:
2018.07.04
城市选择页⾯添加城市列表搜索过滤功能
2018.07.05
openSetting API废弃兼容处理(SDKVersion >= 2.0.7使⽤button,引导⽤户主动打开⼩程序设置页⾯),如下:其他开源⼩程序
感兴趣的可以看下另⼀个开源⼩程序噢:,,欢迎交流学习~~~~。

相关文档
最新文档