微信小程序天气预报(附源码)

叁歲伎倆 2023-07-18 03:17 75阅读 0赞

简介

这是一个完整的已经线上运行的天气应用小程序,点击可查看源码,可随意 star。也可以扫描下方的小程序码直接体验。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70

image

新版首页(可根据不同的天气改变背景)

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 1

image

其他效果图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 2

image

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 3

image

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 4

数据来源

地理编码、天气数据均来自百度地图开放平台。个人开发完全免费,有对应的小程序 sdk,加入即可,但是返回的天气数据较少。

运行前准备

  • 注册微信小程序,获取 appid
  • 气象数据和风天气,需要注册账号获取 key;免费版只能获取三天的天气数据,若要获取七天的气象数据,可以申请个人开发者认证;
  • 在 app.js 中替换 globalData 中的 key 为自己的 key
  • Run

天气数据获取

因为只是一个个人版DEMO(完整版),开发前就决定选择免费的天气数据(个人开发免费),懒得去寻找其他的天气数据,懒得去注册账号,就直接选择了百度地图开放平台的天气数据,正好也提供了小程序对应的 sdk,但是可能相比于其他的天气 API,百度返回的数据偏少:当天 pm2.5、当天和未来三天数据、当天生活指数,其他的就没有了。但是对于一款简单的天气应用小程序来说也够了。

地理编码

获取天气数据默认返回当前城市的天气数据,如果要获取其他的城市的天气数据,需要传入经纬度。为了获取其他城市的经纬度,这里使用的地图的地理编码接口,输入城市名,输出经纬度,然后调用获取天气数据 API 即可。

具体实现

该应用只有五个个页面:首页、设置页、关于页。如下:

首页

首页最终的显示效果是这个样子:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 5

image

从上到下依次是:其他城市天气搜索、当前城市数据展示、当天和未来三天天气数据展示、当天生活指数展示、footer。下拉刷新会刷新当前地区的天气数据。其中,顶部城市天气搜索和生活指数可以在设置中隐藏。屏幕右下角是一个悬浮球菜单,点击后会弹出设置、关于页面的入口。背景色默认是#40a7e7纯色,未来三天天气预报和生活指数分别添加了透明的黑色背景。设计稿?没有的,纯肉眼调试,直到自己看着舒服。

主页面

先定义一个方法获取当前地区的天气数据:

  1. init(params) {
  2. let that = this
  3. let BMap = new bmap.BMapWX({
  4. ak: globalData.ak,
  5. })
  6. BMap.weather({
  7. location: params.location,
  8. fail: that.fail,
  9. success: that.success,
  10. })
  11. },

ak请替换为自己的ak,因为需要获取用户的地理位置,所以在fail的回调中需要处理用户拒绝获取地理位置的逻辑,这里处理为:提示打开地理位置授权,3000mswx.openSetting()跳转到小程序设置页,如下:

  1. fail (res) {
  2. wx.stopPullDownRefresh()
  3. let errMsg = res.errMsg || ''
  4. // 拒绝授权地理位置权限
  5. if (errMsg.indexOf('deny') !== -1 || errMsg.indexOf('denied') !== -1) {
  6. wx.showToast({
  7. title: '需要开启地理位置权限',
  8. icon: 'none',
  9. duration: 3000,
  10. success (res) {
  11. let timer = setTimeout(() => {
  12. clearTimeout(timer)
  13. wx.openSetting({})
  14. }, 3000)
  15. },
  16. })
  17. } else {
  18. wx.showToast({
  19. title: '网络不给力,请稍后再试',
  20. icon: 'none',
  21. })
  22. }
  23. },

获取到用户的地理位置后,执行success

  1. success (data) {
  2. wx.stopPullDownRefresh()
  3. let now = new Date()
  4. // 存下来源数据
  5. data.updateTime = now.getTime()
  6. data.updateTimeFormat = utils.formatDate(now, "MM-dd hh:mm")
  7. let results = data.originalData.results[0] || {}
  8. data.pm = this.calcPM(results['pm25'])
  9. // 当天实时温度
  10. data.temperature = `${results.weather_data[0].date.match(/\d+/g)[2]}`
  11. wx.setStorage({
  12. key: 'cityDatas',
  13. data: data,
  14. })
  15. this.setData({
  16. cityDatas: data,
  17. })
  18. },

看一下返回的天气数据格式:

  1. {
  2. "error": 0,
  3. "status": "success",
  4. "date": "2018-06-29",
  5. "results": [
  6. {
  7. "currentCity": "北京市",
  8. "pm25": "55",
  9. "index": [
  10. {
  11. "des": "天气炎热,建议着短衫、短裙、短裤、薄型T恤衫等清凉夏季服装。",
  12. "zs": "炎热",
  13. "tipt": "穿衣指数",
  14. "title": "穿衣"
  15. },
  16. {
  17. "des": "较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。",
  18. "zs": "较适宜",
  19. "tipt": "洗车指数",
  20. "title": "洗车"
  21. },
  22. {
  23. "des": "各项气象条件适宜,发生感冒机率较低。但请避免长期处于空调房间中,以防感冒。",
  24. "zs": "少发",
  25. "tipt": "感冒指数",
  26. "title": "感冒"
  27. },
  28. {
  29. "des": "天气较好,无雨水困扰,但考虑气温很高,请注意适当减少运动时间并降低运动强度,运动后及时补充水分。",
  30. "zs": "较不宜",
  31. "tipt": "运动指数",
  32. "title": "运动"
  33. },
  34. {
  35. "des": "属中等强度紫外线辐射天气,外出时建议涂擦SPF高于15、PA+的防晒护肤品,戴帽子、太阳镜。",
  36. "zs": "中等",
  37. "tipt": "紫外线强度指数",
  38. "title": "紫外线强度"
  39. }
  40. ],
  41. "weather_data": [
  42. {
  43. "date": "周五 06月29日 (实时:34℃)",
  44. "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
  45. "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/qing.png",
  46. "weather": "多云转晴",
  47. "wind": "东南风微风",
  48. "temperature": "38 ~ 25℃"
  49. },
  50. {
  51. "date": "周六",
  52. "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
  53. "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
  54. "weather": "多云",
  55. "wind": "东南风微风",
  56. "temperature": "36 ~ 23℃"
  57. },
  58. {
  59. "date": "周日",
  60. "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/qing.png",
  61. "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/qing.png",
  62. "weather": "晴",
  63. "wind": "东南风微风",
  64. "temperature": "35 ~ 23℃"
  65. },
  66. {
  67. "date": "周一",
  68. "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/qing.png",
  69. "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
  70. "weather": "晴转多云",
  71. "wind": "南风微风",
  72. "temperature": "35 ~ 25℃"
  73. }
  74. ]
  75. }
  76. ]
  77. }

success里缓存了最新一次获取的天气数据+更新的时间cityDatas,小程序的模板里无法使用方法,所以数据需要在js里面先格式化。calcPM用来计算当前 pm2.5 的质量,返回“优良差”类似字样,范围标准可自行搜索。当天的实时温度并没有给出独立的字段,而是混在了wearther_data[0]data字段里:"date": "周五 06月29日 (实时:34℃)",需要自行提取。返回的天气 icon 和色调不搭,就没有使用。其他的数据按照按照我们要显示的格式直接填充即可。

城市天气搜索

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

https://api.map.baidu.com/geocoder/v2/?address=${address}&output=json&ak=${yourak}

返回的数据格式为:

  1. {
  2. "status":0,
  3. "result":{
  4. "location":{
  5. "lng":117.21081309155257,
  6. "lat":39.143929903310074
  7. },
  8. "precise":0,
  9. "confidence":12,
  10. "level":"城市"
  11. }
  12. }

然后直接调用获取天气 API 即可。具体代码如下:

  1. geocoder (address, success) {
  2. let that = this
  3. wx.request({
  4. url: getApp().setGeocoderUrl(address),
  5. success (res) {
  6. let data = res.data || {}
  7. if (!data.status) {
  8. let location = (data.result || {}).location || {}
  9. // location = {lng, lat}
  10. success && success(location)
  11. } else {
  12. wx.showToast({
  13. title: data.msg || '网络不给力,请稍后再试',
  14. icon: 'none',
  15. })
  16. }
  17. },
  18. fail (res) {
  19. wx.showToast({
  20. title: res.errMsg || '网络不给力,请稍后再试',
  21. icon: 'none',
  22. })
  23. },
  24. complete () {
  25. that.setData({
  26. searchText: '',
  27. })
  28. },
  29. })
  30. },
  31. search (val) {
  32. // 动画
  33. if (val === '520' || val === '521') {
  34. this.setData({
  35. searchText: '',
  36. })
  37. this.dance()
  38. return
  39. }
  40. wx.pageScrollTo({
  41. scrollTop: 0,
  42. duration: 300,
  43. })
  44. if (val) {
  45. let that = this
  46. this.geocoder(val, (loc) => {
  47. that.init({
  48. location: `${loc.lng},${loc.lat}`
  49. })
  50. })
  51. }
  52. },

搜索动画彩蛋

在搜索框里搜索520521,会出现从顶部下小心心的动画,如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 6

image

这里实现比较简单。

创建了一个heartbeat的组件。wxml结构是遍历数组,创建多个大小、位置随机的图片:

  1. <image wx:for='{
  2. {arr}}' wx:key='{
  3. {index}}' animation='{
  4. {animations[index]}}' class='heart' style='left:{
  5. {lefts[index]}}px;top:{
  6. {tops[index]}}px;width:{
  7. {widths[index]}}rpx;height:{
  8. {widths[index]}}rpx;' src='/img/heartbeat.png'></image>

然后使用的是小程序提供的wx.createAnimation,动画的使用比较简单,创建动画,然后赋予animation属性即可,比较简单,但是也有局限性,比如,没有直接的动画结束后的回调,但是可以使用setTimeout来实现等。这里会用到可用窗口宽高,因为多处用到了该参数,所以在app.js里面异步获取了先。

动画代码如下:

  1. dance (callback) {
  2. let windowWidth = this.data.windowWidth
  3. let windowHeight = this.data.windowHeight
  4. let duration = this.data.duration
  5. let animations = []
  6. let lefts = []
  7. let tops = []
  8. let widths = []
  9. let obj = {}
  10. for (let i = 0; i < this.data.arr.length; i++) {
  11. lefts.push(Math.random() * windowWidth)
  12. tops.push(-140)
  13. widths.push(Math.random() * 50 + 40)
  14. let animation = wx.createAnimation({
  15. duration: Math.random() * (duration - 1000) + 1000
  16. })
  17. animation.top(windowHeight).left(Math.random() * windowWidth).rotate(Math.random() * 960).step()
  18. animations.push(animation.export())
  19. }
  20. this.setData({
  21. lefts,
  22. tops,
  23. widths,
  24. })
  25. let that = this
  26. let timer = setTimeout(() => {
  27. that.setData({
  28. animations,
  29. })
  30. clearTimeout(timer)
  31. }, 200)
  32. let end = setTimeout(() => {
  33. callback && callback()
  34. clearTimeout(end)
  35. }, duration)
  36. },
  37. },

首页搜索特定关键词后,调用组件dance方法即触发小心心动画。

悬浮球菜单

屏幕右下角的悬浮球提供了三个页面的入口:城市选择页、设置页、关于页。菜单弹出、收回会有动画。

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

  1. // wxml
  2. <!-- 悬浮菜单 -->
  3. <view class='menus'>
  4. <image src="/img/location.png" animation="{
  5. {animationOne}}" class="menu" bindtap="menuOne" style='top:{
  6. {pos.top}}px;left:{
  7. {pos.left}}px;'></image>
  8. <image src="/img/setting.png" animation="{
  9. {animationTwo}}" class="menu" bindtap="menuTwo" style='top:{
  10. {pos.top}}px;left:{
  11. {pos.left}}px;'></image>
  12. <image src="/img/info.png" animation="{
  13. {animationThree}}" class="menu" bindtap="menuThree" style='top:{
  14. {pos.top}}px;left:{
  15. {pos.left}}px;'></image>
  16. <image src="/img/menu.png" animation="{
  17. {animationMain}}" class="menu main" bindtap="menuMain" catchtouchmove='menuMainMove' style='top:{
  18. {pos.top}}px;left:{
  19. {pos.left}}px;'></image>
  20. </view>
  21. // js
  22. popp() {
  23. let animationMain = wx.createAnimation({
  24. duration: 200,
  25. timingFunction: 'ease-out'
  26. })
  27. let animationOne = wx.createAnimation({
  28. duration: 200,
  29. timingFunction: 'ease-out'
  30. })
  31. let animationTwo = wx.createAnimation({
  32. duration: 200,
  33. timingFunction: 'ease-out'
  34. })
  35. let animationThree = wx.createAnimation({
  36. duration: 200,
  37. timingFunction: 'ease-out'
  38. })
  39. animationMain.rotateZ(180).step()
  40. animationOne.translate(-50, -60).rotateZ(360).opacity(1).step()
  41. animationTwo.translate(-90, 0).rotateZ(360).opacity(1).step()
  42. animationThree.translate(-50, 60).rotateZ(360).opacity(1).step()
  43. this.setData({
  44. animationMain: animationMain.export(),
  45. animationOne: animationOne.export(),
  46. animationTwo: animationTwo.export(),
  47. animationThree: animationThree.export(),
  48. })
  49. },

悬浮菜单是可以在屏幕上随意滑动的,方法也很简单,监听touchmove事件即可,因为菜单展开方向是在左边,所以悬浮菜单能往左边移动的最远距离要有一段间隔,否则展开的菜单就进入左边屏幕了,移动到上方同样逻辑(后期可以改成菜单展开方向随移动而改变,而不是一味在左边展开)。

代码如下:

  1. menuMainMove (e) {
  2. // 如果已经弹出来了,需要先收回去,否则会受 top、left 会影响
  3. if (this.data.hasPopped) {
  4. this.takeback()
  5. this.setData({
  6. hasPopped: false,
  7. })
  8. }
  9. let windowWidth = SYSTEMINFO.windowWidth
  10. let windowHeight = SYSTEMINFO.windowHeight
  11. let touches = e.touches[0]
  12. let clientX = touches.clientX
  13. let clientY = touches.clientY
  14. // 边界判断
  15. if (clientX > windowWidth - 40) {
  16. clientX = windowWidth - 40
  17. }
  18. if (clientX <= 90) {
  19. clientX = 90
  20. }
  21. if (clientY > windowHeight - 40 - 60) {
  22. clientY = windowHeight - 40 - 60
  23. }
  24. if (clientY <= 60) {
  25. clientY = 60
  26. }
  27. let pos = {
  28. left: clientX,
  29. top: clientY,
  30. }
  31. this.setData({
  32. pos,
  33. })
  34. },

至于一些样式、逻辑上的细节,这里不再赘述,具体可查看源码。

关于页

关于页是一个展示页,没有多少交互,使用到的 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掉即可。现在设置的是本地只保存一张图片,所以重新设置其他背景时,会删除上一张背景图,然后重新保存新背景图。

实现如下:

  1. defaultBcg () {
  2. this.removeBcg(() => {
  3. wx.showToast({
  4. title: '恢复默认背景',
  5. duration: 1500,
  6. })
  7. })
  8. },
  9. removeBcg (callback) {
  10. wx.getSavedFileList({
  11. success: function (res) {
  12. let fileList = res.fileList
  13. let len = fileList.length
  14. if (len > 0) {
  15. for (let i = 0; i < len; i++)
  16. (function (path) {
  17. wx.removeSavedFile({
  18. filePath: path,
  19. complete: function (res) {
  20. if (i === len - 1) {
  21. callback && callback()
  22. }
  23. }
  24. })
  25. })(fileList[i].filePath)
  26. } else {
  27. callback && callback()
  28. }
  29. },
  30. fail: function () {
  31. wx.showToast({
  32. title: '出错了,请稍后再试',
  33. icon: 'none',
  34. })
  35. },
  36. })
  37. },
  38. customBcg () {
  39. let that = this
  40. wx.chooseImage({
  41. success: function (res) {
  42. that.removeBcg(() => {
  43. wx.saveFile({
  44. tempFilePath: res.tempFilePaths[0],
  45. success: function (res) {
  46. wx.navigateBack({})
  47. },
  48. })
  49. })
  50. },
  51. fail: function (res) {
  52. let errMsg = res.errMsg
  53. // 如果是取消操作,不提示
  54. if (errMsg.indexOf('cancel') === -1) {
  55. wx.showToast({
  56. title: '发生错误,请稍后再试',
  57. icon: 'none',
  58. })
  59. }
  60. },
  61. })
  62. },
  • 打开顶部城市天气快捷搜索

该操作只是将首页的顶部搜索wx:if掉而已。switch组件的样式可以通过修改默认的类来修改,调一个自己满意的即可:

  1. .wx-switch-input{width:84rpx !important;height:43rpx !important;}
  2. .wx-switch-input::before{width:82rpx !important;height: 38rpx !important;}
  3. .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.getScreenBrightnesswx.setScreenBrightnesswx.setKeepScreenOn。完整实现可查看源码。

3)系统信息

系统信息会跳转到新页面。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 7

其他

其他代码细节,不再赘述,具体可查看源码。
作者:jack.zhang
GitHub:https://github.com/zhangliwen1101/WeatherWidget

智能垃圾分类,源码,扫码直接体验

aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3poYW5nbGl3ZW4xMTAxL0ltYWdlcy9tYXN0ZXIvaW1nL2dhcmJhZ2UvZXJ3ZWltYTAxLmpwZw

定制属于你自己的婚礼邀请函,源码,扫描直接体验

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x3emhhbmcxMTAx_size_16_color_FFFFFF_t_70 8

扫码关注,免去各种VIP,直接观看各种影视资源

aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3poYW5nbGl3ZW4xMTAxL0ltYWdlcy9tYXN0ZXIvaW1nL2dvbmd6aW5naGFvLmpwZw

需要入群交流的,可以加我微信: bjawenfd ,纯粹交流群互粉群

发表评论

表情:
评论列表 (有 0 条评论,75人围观)

还没有评论,来说两句吧...

相关阅读