这是我参与更文挑战的第5天,活动详情查看: 更文挑战
有了之前的框架整理和基本农历功能,接着就可以在 日历 View 里增加显示天气情况。
天气预报
实现天气预报的功能,还是比较简单得。
- 从天气预报服务商实时获取天气信息;
- 数据缓存;
- 显示在页面中;
- 设置是否显示天气预报的开关。
和风天气
在国内提供天气预报 API/SDK 的平台不少,其中「和风天气」是佼佼者。
开发具体看:和风天气开发平台
服务端开发
服务端主要使用到和风天气 API 的有:
- 城市信息查询接口:dev.qweather.com/docs/api/ge…
- 实时天气接口:dev.qweather.com/docs/api/we…
- 逐天(3 天)天气预报接口:dev.qweather.com/docs/api/we…
- 实时空气质量接口: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,欢迎查看!
最后感谢自己能坚持到第五天,也欢迎大家看看前四天的记录,对本项目有个初步的了解:
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!