微信小程序天气预报

显示效果X3:

天气预报

微信小程序本身(2023.01)并未提供天气预报功能,但允许接入高德(或其他)的天气预报。
接入高德坏处是能获取的天气信息比较有限,只能提供今日实时天气和未来3日天气预报。
每日可调用次数3,000,000次,对小项目来说完全够用。

实际上,高德提供了至少2种可接入方式,1、插件,2、网络接口。而插件本质也是调用网络接口(呵呵),这里使用的是插件。(高德插件文档

接入准备

1、在高德控制台创建一个应用,然后添加key
2、在微信小程序(官网)后台,开发管理->开发设置->服务器域名->request合法域名里
添加白名单'https://restapi.amap.com;'

代码

weather.js

const app = getApp()
const amap = require('../../../utils/amap-wx.130')
const util = require('../../../utils/util')
Page({
  data: {
    // 天气
    weather: {},
    casts:[], 
    myAmap: {}, // 高德-天气预报工具
  },
  onLoad(options) {
    this.data.myAmap = new amap.AMapWX({key:app.globalData.amap_key});  // 你在高德注册的key
  },
  onShow() {
    let city = '北京市'
    this.requestWeather(city)
  },
/* 
    绘图思路:先定x,y轴,分别在左边和底部留出空隙(x_left & y_bottom);右边也留点,顶部也留点(y_top)
    x轴绘制:从y_top画到y_data_bottom;这一段是【实线】,所有温度处于其间。上下顶点分别是最高温和最低温值。
          【虚线】部分从y_data_bottom到y_bottom;因为最小温度不定,所以无法确定x,y轴交点温度是多少,所以留一段虚线;这样实线底端可表示任意温度值。
    数据绘制:天气数据的(x,y),x好确定,把x轴长度分均分成casts.length分就行了(实际要略短,因为要在右边留一点距离)
             y值稍麻烦。casts最低温度为tp_min,最高温度为tp_max,温差tp_diff = tp_max - tp_min,
             y轴实线底端表示的温度即tp_min,y轴实线总长表示温差tp_diff。
             根据这个关系,对任意温度tp_d,有 let len = (tp_d - tp_min)*400/tp_diff,len换算成单位px得到len_px,
             从y轴实线底端y_data_bottom往上回退len_px,就得到了当前温度值的y坐标
  */
  drawCanvas(canvas) {
    let casts = this.data.casts
    if(casts.length < 3) {
      return
    }
    let _this = this
    const query = wx.createSelectorQuery()
    query.select('#myCanvas2')
      .fields({ node: true, size: true })
      .exec((res) => {
        // 初始化
        const canvas = res[0].node
        const ctx = canvas.getContext('2d')
        const dpr = wx.getSystemInfoSync().pixelRatio
        canvas.width = res[0].width * dpr
        canvas.height = res[0].height * dpr
        ctx.scale(dpr, dpr)
        // --------------- x,y坐标轴 ---------------
        let x_left = util.width_of_px_from_rpx(50)
        let x_right = util.width_of_px_from_rpx(700)
        let y_top = util.width_of_px_from_rpx(60)
        let y_bottom = util.width_of_px_from_rpx(530)
        let y_data_len = util.width_of_px_from_rpx(400)   // 数据区域高度
        let y_data_bottom = y_top + y_data_len
        let dash_gap = util.width_of_px_from_rpx(10)
        let axis_w = util.width_of_px_from_rpx(3)
        ctx.beginPath()
        ctx.strokeStyle = '#C1CDC1'
        ctx.lineWidth = axis_w
        ctx.moveTo(x_left, y_top)
        ctx.lineTo(x_left, y_data_bottom)
        // --------------- dash line ---------------
        ctx.stroke()
        ctx.beginPath()
        ctx.strokeStyle = '#C1CDCD'
        for(let i = 0; true; i++) {
          if(i%2 == 0) {
            ctx.moveTo(x_left, y_data_bottom + dash_gap*i)
            ctx.lineTo(x_left, y_data_bottom + dash_gap*(i+1))
          }
          if(y_data_bottom + dash_gap*(i+1) > y_bottom) {
            break
          }
        }
        ctx.stroke()
        ctx.beginPath()
        ctx.strokeStyle = '#C1CDC1'
        ctx.moveTo(x_left, y_bottom)
        ctx.lineTo(x_right, y_bottom)
        ctx.stroke()
        // --------------- 数据 ---------------
        // let y_gap = util.width_of_px_from_rpx(80) // y_data_len = 400rpx。分成4分
        let tp_min = 100  // 最低温度,初始值一定要比casts任何温度高
        let tp_max = -100  // 最高温度,初始值一定要比casts任何温度低
        for(let i = 0; i < casts.length; i++) {
          let item = casts[i]
          let tp_d = Number(item.daytemp)
          let tp_n = Number(item.nighttemp)
          if(tp_d < tp_min) {tp_min = tp_d}
          if(tp_n < tp_min) {tp_min = tp_n}
          if(tp_d > tp_max) {tp_max = tp_d}
          if(tp_n > tp_max) {tp_max = tp_n}
        }
        let tp_diff = tp_max - tp_min // 温差
        if(tp_diff == 0){tp_diff = 1} // 理论上分母 tp_diff 不能为0;实际也可不处理,因为不可能4天温差为0。
        let fontPX = Math.floor(util.width_of_px_from_rpx(22))
        ctx.font = 'normal ' + fontPX + 'px 微软雅黑'
        // 白天 曲线
        _this.drawBrokenLine(0, ctx, casts, tp_min, tp_diff, x_left, y_bottom, y_data_bottom)
        // 黑夜 曲线
        _this.drawBrokenLine(1, ctx, casts, tp_min, tp_diff, x_left, y_bottom, y_data_bottom)
      })
  },
  // 绘制折线
  /**
   * @param {*} type 0-白天,1-晚上
   * @param {*} ctx canvas context
   * @param {*} casts 天气预报信息(今日+3日),数组,length = 4。
   * @param {*} tp_min casts 内最低温度(白天、黑夜最低)
   * @param {*} tp_diff 温差:casts 中最高温度 - 最低温度
   * @param {*} x_left x轴起点 在 canvas 的实际坐标 单位px
   * @param {*} y_bottom y轴最低点 在 canvas 的实际坐标 单位px
   * @param {*} y_data_bottom 实际数据最低值(虚线以上部分) 在 canvas 的实际坐标 单位px
   */
  drawBrokenLine(type, ctx, casts, tp_min, tp_diff, x_left, y_bottom, y_data_bottom) {
    ctx.beginPath()
    type == 0 ? ctx.strokeStyle = '#FF8C00' : ctx.strokeStyle = '#8B7D6B'
    let color = type == 0 ? '#8A2BE2' : '#4169E1'
    let txt_offset_y = util.width_of_px_from_rpx(30)
    let day_offset_y = txt_offset_y + util.width_of_px_from_rpx(30)
    let date_offset_x = util.width_of_px_from_rpx(28)
    let week_offset_x = util.width_of_px_from_rpx(18)
    let days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']

    let dot_r = util.width_of_px_from_rpx(10)   // 数据点的小圆的半径
    let x_gap = util.width_of_px_from_rpx(200)  // x轴2个数据的间距,分成5分
    let txt_offset_x = util.width_of_px_from_rpx(40)
    for(let i = 0; i < casts.length; i++) {
      let item = casts[i]
      let tp_d = type == 0 ? Number(item.daytemp) : Number(item.nighttemp)
      // len:400 = (tp_d - tp_min):tp_diff
      let len = (tp_d - tp_min)*400/tp_diff // 温度tp_d对应在y轴的高度   
      let len_c = Math.floor(len)
      let len_px = util.width_of_px_from_rpx(len_c)
      let x = x_left + x_gap*i
      let y = y_data_bottom - len_px
      if(i == 0) {
        ctx.moveTo(x, y)
      }else {
        ctx.lineTo(x, y)
      }
      ctx.stroke()
    }
    for(let i = 0; i < casts.length; i++) {
      let item = casts[i]
      let tp_d = type == 0 ? Number(item.daytemp) : Number(item.nighttemp)
      let len = (tp_d - tp_min)*400/tp_diff // 温度tp_d对应在y轴的高度
      let len_c = Math.floor(len)
      let len_px = util.width_of_px_from_rpx(len_c)
      let x = x_left + x_gap*i
      let y = y_data_bottom - len_px
      ctx.beginPath()
      // 圆点
      ctx.arc(x, y, dot_r, 0, Math.PI*2)
      ctx.fillStyle = color
      ctx.fill()
      ctx.stroke()
      // 文字
      let tp = tp_d + '°,' + (type == 0 ? item.dayweather : item.nightweather)
      let offset_y = type == 0 ? -txt_offset_y : (txt_offset_y*1.2)
      ctx.fillText(tp, x - txt_offset_x, y + offset_y)
      if(type == 0) {
        if(item.date && item.date.length == 10) { // 日期
          let dateStr = item.date.substr(5, 5)
          ctx.fillText(dateStr, x - date_offset_x, y_bottom + txt_offset_y)
        }
      }else {
        if(item.week && item.week <= 7) { // 星期几
          let weekStr = days[item.week - 1]
          ctx.fillText(weekStr, x - week_offset_x, y_bottom + day_offset_y)
        }
      }
    }
  },
  // -------------------------- request ---------------------------
  requestWeather(city) {
    if(!city) {
      return
    }
    let _this = this
    // 获取实时天气
    this.data.myAmap.getWeather({
      city: city,
      success: function(data){
        _this.setData({weather: data})
      },
      fail: function(info){   }
    })
    //  获取3日天气预报
    this.data.myAmap.getWeather({
      city: city,
      type: 'forecast',
      success: function(data){
        if(data.forecast && data.forecast.casts) {
          _this.setData({casts: data.forecast.casts})
          _this.drawCanvas()
        }
      },
      fail: function(info){   }
    })    
  }
})

插件本身没相关文档,因为插件本质调用https://restapi.amap.com/v3/weather/weatherInfo,所以可以参考Web服务 API(注意,虽然是同一个接口,但插件可能对结果重新封装了,数据要以实际为准,不一定100%同这个文档)。
至于getWeather({..type: 'forecast'..}) 方法里type
不传该字段就能获取实时天气,传了forecast就是3日天气预报(+今日=4日)

文档缺失,只能东搞搞西搞搞瞎搞出来要的东西。
/**
forcast 对象说明。
date 日期
week 星期几
dayweather 白天天气现象
nightweather 晚上天气现象
daytemp 白天温度
nighttemp 晚上温度
daywind 白天风向
nightwind 晚上风向
daypower 白天风力
nightpower 晚上风力
*/
weather.wxml

<view class="container">
  <block wx:if="{{weather.liveData}}">
    <view class="rowh15"></view>
    <view class="city flex-r-end">
      <view class="columnw10"></view>
      <view class="">{{weather.city.text}}:</view>
      <view class="">{{weather.city.data}}</view>
    </view>
    <view class="rowh40"></view>
    <view class="flex-r-end">
      <view class="key1">天气:</view>
      <view class="value1">{{weather.liveData.weather}}</view>
      <view class="columnw50"></view>
      <view class="key1">温度:</view>
      <view class="value1">{{weather.liveData.temperature}}°</view>
    </view>
    <view class="rowh10"></view>
    <view class="flex-r-end">
      <view class="key2">风力:</view>
      <view class="value2">{{weather.liveData.windpower}}</view>
      <view class="columnw50"></view>
      <view class="key2">风向:</view>
      <view class="value2">{{weather.winddirection.data}}</view>
    </view>
    <view class="rowh15"></view>
    <view class="flex-r-end">
      <view class="key2">{{weather.humidity.text}}:</view>
      <view class="value2">{{weather.humidity.data}}</view>
    </view>
  </block>
  <view class="rowh50"></view>
  <view class="forecast-title flex-center">未来3日天气预报</view>
  <view class="flex-r tips">
    <view class="day-line">橙色线表示-白天温度</view>
    <view class="night-line">灰色线表示-夜间温度</view>
  </view>
  <canvas class="canvas" type="2d" id="myCanvas2" disable-scroll="true"></canvas>
</view>

weather.wxss

page {
  --fs-medium: 30rpx;       
  --fs-tiny: 22rpx;
  --theme-blue: #4272EA;
}
.container{
  position: absolute;
  top: 0rpx;
  bottom: 0;
  left: 0;
  right: 0;
  background: #4D8AD7;
  color: #fff;
  font-size: 18px;
  box-sizing: border-box;
  padding-left: 0rpx;
}
.city {
  font-size: var(--fs-tiny);
  color: #eeeeee;
}
.key1 {
  /* color: var(--gray-8f); */
  margin-left: 30rpx;
  color: #e0e0e0;
  font-size: 30rpx;
}
.value1 {
  font-size: 50rpx;
  color: white;
}
.key2 {
  margin-left: 30rpx;
  font-size: 26rpx;
  color: #e0e0e0;
}
.value2 {
  font-size: 26rpx;
  color: #e0e0e0;
}
.forecast-title {
  width: 100vmin;
  height: 70rpx;
  font-size: var(--fs-medium);
  background-color: white;
  color: var(--theme-blue);
}
.tips {
  width: 100vmin;
  background-color: white;
}
.day-line {
  font-size: var(--fs-tiny);
  color: #FF8C00;
  margin-left: 20rpx;
}
.night-line {
  font-size: var(--fs-tiny);
  color: #8B7D6B;
  margin-left: 40rpx;
}
.canvas {
  width: 750rpx;
  height: 600rpx;
  background-color: white;
}
.rowh10 {width: 100%;height: 10rpx;}
.rowh15 {width: 100%;height: 15rpx;}
.rowh40 {width: 100%;height: 40rpx;}
.rowh50 {width: 100%;height: 50rpx;}
.columnw10 {width: 10rpx;height: 100%;}
.columnw50 {width: 50rpx;height: 100%;}
.flex-r {display: flex;align-items: flex-start;}
.flex-r-end {display: flex;align-items: flex-end;}
.flex-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

工具类../../../utils/util.js

const screenWidth = wx.getSystemInfoSync().screenWidth
// 将 rpx 单位长度换算成 px。
function width_of_px_from_rpx(value) {
  let value_of_px = value/750.0*screenWidth
  return value_of_px
}
module.exports = {
  width_of_px_from_rpx:width_of_px_from_rpx
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容