阅读 710

Electron+Vue3 MAC 版日历 开发记录(5)——天气预报

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

有了之前的框架整理和基本农历功能,接着就可以在 日历 View 里增加显示天气情况。

天气预报

实现天气预报的功能,还是比较简单得。

  1. 从天气预报服务商实时获取天气信息;
  2. 数据缓存;
  3. 显示在页面中;
  4. 设置是否显示天气预报的开关。

和风天气

在国内提供天气预报 API/SDK 的平台不少,其中「和风天气」是佼佼者。

和风天气 API 为用户提供一个简洁的 RESTful API 接口,用以访问基于位置的天气数据

开发具体看:和风天气开发平台

服务端开发

服务端主要使用到和风天气 API 的有:

  1. 城市信息查询接口:dev.qweather.com/docs/api/ge…
  2. 实时天气接口:dev.qweather.com/docs/api/we…
  3. 逐天(3 天)天气预报接口:dev.qweather.com/docs/api/we…
  4. 实时空气质量接口:dev.qweather.com/docs/api/ai…

具体 Laravel 代码:

<?php
/**
 * User: yemeishu
 */

namespace App\Http\Controllers;

use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use Illuminate\Http\Request;
use GuzzleHttp\Psr7\Request as GRequest;
use Illuminate\Support\Arr;
use function Matrix\add;

class WeatherController extends Controller
{
    private $counter = 1;
    private $result = [];
    private $public_id = "***";
    private $key = "***";

    // 和风天气开发平台
    // https://dev.qweather.com/docs/api/weather/
    // 实况接口
    private $now_finance_api = "https://api.qweather.com/v7/weather/now";
    private $now_dev_api = "https://devapi.qweather.com/v7/weather/now";

    // 3天预报接口
    private $td_finance_api = "https://api.qweather.com/v7/weather/3d";
    private $td_dev_api = "https://devapi.qweather.com/v7/weather/3d";

    // 空气质量接口
    private $air_finance_api = "https://api.qweather.com/v7/air/now";
    private $air_dev_api = "https://devapi.qweather.com/v7/air/now";

    // 城市信息搜索接口
    // 需要存于数据库,避免多次查询接口
    // 每天每个账号下所有应用前50000次免费
    private $city_lookup_api = "https://geoapi.qweather.com/v2/city/lookup";

    // 默认以石家庄经纬度为例
    private $coordinate_default = "114.48,38.03";

    private $location;

    private $finance_api = [
        "https://geoapi.qweather.com/v2/city/lookup",
        "https://api.qweather.com/v7/weather/now",
        "https://api.qweather.com/v7/weather/3d",
        "https://api.qweather.com/v7/air/now"
    ];

    private $dev_api = [
        "https://geoapi.qweather.com/v2/city/lookup",
        "https://devapi.qweather.com/v7/weather/now",
        "https://devapi.qweather.com/v7/weather/3d",
        "https://devapi.qweather.com/v7/air/now"
    ];

    public function weatherData(Request $request)
    {
        $this->location = $request->input('location', $this->coordinate_default);

        $client = new Client();

        $requests = function ($apis) {
            foreach ($apis as $api) {
                yield new GRequest(
                    'GET',
                    "$api?location=$this->location&key=$this->key"
                );
            }
        };

        $pool = new Pool(
            $client,
            $requests($this->dev_api),
            [
            'concurrency' => 3,
            'fulfilled' => function ($response, $index) {
                $data = json_decode($response->getBody()->getContents(), true);
                $this->change2result($data);
                if ($this->countedAndCheckEnded($data)) {
                    return $this->result;
                }
            },
            'rejected' => function ($reason, $index) {
                info('weather rejected', [$reason]);
                // this is delivered each failed request
            },
        ]);

        $promise = $pool->promise();

        $promise->wait();

        return $this->result;
    }

    private function countedAndCheckEnded($data)
    {
        if ($this->counter < count($this->dev_api)){
            $this->counter++;
            return false;
        }
        return true;
    }

    private function change2result($data)
    {
        if ($data['code'] == 200) {
            if (Arr::has($data, 'updateTime')) {
                $this->result['updateTime'] = (new Carbon($data['updateTime']))->toDateTimeString();
            }

            if (Arr::has($data, 'now')) {
                foreach ($data['now'] as $key => $value) {
                    $this->result['weatherNow'][$key] = $value;
                }
            }

            if (Arr::has($data, 'daily')) {
                $this->result['weatherDailies'] = $data['daily'];
            }

            if (Arr::has($data, 'location')) {
                $this->result['locations'] = $data['location'];
            }
        }
    }
}
复制代码

默认使用我自己所在地区的经纬度,接口返回数据如下:

{
    locations: [
        {
            name: "桥西",
            id: "101090120",
            lat: "38.02838135",
            lon: "114.46292877",
            adm2: "石家庄",
            adm1: "河北省",
            country: "中国",
            tz: "Asia/Shanghai",
            utcOffset: "+08:00",
            isDst: "0",
            type: "city",
            rank: "35",
            fxLink: "http://hfx.link/1toj1"
        }
    ],
    updateTime: "2021-05-18 15:58:00",
    weatherNow: {
        obsTime: "2021-05-18T15:53+08:00",
        temp: "30",
        feelsLike: "26",
        icon: "100",
        text: "晴",
        wind360: "135",
        windDir: "东南风",
        windScale: "5",
        windSpeed: "32",
        humidity: "33",
        precip: "0.0",
        pressure: "994",
        vis: "20",
        cloud: "10",
        dew: "10",
        pubTime: "2021-05-18T15:00+08:00",
        aqi: "79",
        level: "2",
        category: "良",
        primary: "O3",
        pm10: "56",
        pm2p5: "31",
        no2: "17",
        so2: "16",
        co: "0.9",
        o3: "183"
    },
    weatherDailies: [
        {
            fxDate: "2021-05-18",
            sunrise: "05:10",
            sunset: "19:28",
            moonrise: "10:04",
            moonset: "01:00",
            moonPhase: "峨眉月",
            tempMax: "30",
            tempMin: "17",
            iconDay: "100",
            textDay: "晴",
            iconNight: "101",
            textNight: "多云",
            wind360Day: "180",
            windDirDay: "南风",
            windScaleDay: "3-4",
            windSpeedDay: "16",
            wind360Night: "0",
            windDirNight: "北风",
            windScaleNight: "1-2",
            windSpeedNight: "3",
            humidity: "34",
            precip: "0.0",
            pressure: "993",
            vis: "25",
            cloud: "0",
            uvIndex: "10"
        },
        {
            fxDate: "2021-05-19",
            sunrise: "05:09",
            sunset: "19:28",
            moonrise: "11:08",
            moonset: "01:35",
            moonPhase: "峨眉月",
            tempMax: "30",
            tempMin: "18",
            iconDay: "302",
            textDay: "雷阵雨",
            iconNight: "350",
            textNight: "阵雨",
            wind360Day: "180",
            windDirDay: "南风",
            windScaleDay: "3-4",
            windSpeedDay: "16",
            wind360Night: "0",
            windDirNight: "北风",
            windScaleNight: "1-2",
            windSpeedNight: "3",
            humidity: "55",
            precip: "2.5",
            pressure: "991",
            vis: "24",
            cloud: "60",
            uvIndex: "5"
        },
        {
            fxDate: "2021-05-20",
            sunrise: "05:08",
            sunset: "19:29",
            moonrise: "12:14",
            moonset: "02:06",
            moonPhase: "上弦月",
            tempMax: "28",
            tempMin: "18",
            iconDay: "101",
            textDay: "多云",
            iconNight: "150",
            textNight: "晴",
            wind360Day: "0",
            windDirDay: "北风",
            windScaleDay: "1-2",
            windSpeedDay: "3",
            wind360Night: "0",
            windDirNight: "北风",
            windScaleNight: "1-2",
            windSpeedNight: "3",
            humidity: "83",
            precip: "0.0",
            pressure: "994",
            vis: "24",
            cloud: "25",
            uvIndex: "11"
        }
    ]
}
复制代码

以上这些天气数据够我们当下使用了。

数据请求

在本项目中,主要使用带有缓存功能的 axios-cache-plugin,同样的封装为 WeatherService

'use strict';
import axios from 'axios';
import wrapper from 'axios-cache-plugin';
export default class WeatherService {
  // 这里是使用 async/await
  async getWeathers(location: any) {
    const locationStr = location.longitude + ',' + location.latitude;
    const http = wrapper(axios, {
      maxCacheSize: 15,
      ttl: 7200000, //ms 数据缓存 2 小时
    });
    http.__addFilter(/weatherdata/);

    const res = await http({
      url: 'https://***.com/weatherdata',
      method: 'get',
      params: {
        param: JSON.stringify({
          location: locationStr,
        }),
      },
    });

    return res.data;
  }
}
复制代码

显示到 View 中

剩下的就比较简单了,就是怎么显示到 View 中了,还是在:

const dateWeather = this.showWeather(date, weather);
if (dateWeather == undefined) {
  return {
    html: `<div class="fc-daygrid-day-number">${dayNumberText}</div>
        <div class="fc-daygrid-day-chinese">${dayTextInChinese}</div>`,
  };
} else {
  const imgSrc = weathericons + '/../' + dateWeather.iconDay +'.png';
  return {
    html: `<div class="fc-daygrid-day-number">${dayNumberText}</div>
      <div class="fc-daygrid-day-chinese">${dayTextInChinese}</div>
      <div class="fc-daygrid-dayweather">
        <img class="fc-daygrid-dayweather-iconday" src=${imgSrc}/>
        <span class="fc-daygrid-dayweather-temp">${dateWeather.textDay} ${dateWeather.tempMin}-${dateWeather.tempMax}°C</span>
      </div>`,
  };
}
复制代码

因为只显示 3 天的天气预报,所以还需要判断可否显示天气:

showWeather(date: Date, weather: any) {

if (weather == null || weather.weatherDailies == null) {
  return undefined;
}
const dateString = Moment(date).format('YYYY-MM-DD');
const result = weather.weatherDailies.find((dateWeather: { fxDate: string; }) => dateWeather.fxDate == dateString);
return result;
}
复制代码

来看看显示结果:

小结

关于开关控制是否显示天气的设置功能,下一步继续整理。

具体代码也放在 Github 上 fanly/fanlymenu,欢迎查看!

最后感谢自己能坚持到第五天,也欢迎大家看看前四天的记录,对本项目有个初步的了解:

Electron+Vue3 MAC 版日历开发记录(1)

Electron+Vue3 MAC 版日历开发记录(2)——功能清单

Electron+Vue3 MAC 版日历开发记录(3)——PrimeVue

Electron+Vue3 MAC 版日历 开发记录(4)——农历功能

文章分类
前端
文章标签