From 2521db74a98cd72fef3594cbfb0885b512514cff Mon Sep 17 00:00:00 2001 From: LIRUI <298977887@qq.com> Date: Fri, 28 Feb 2025 23:28:56 +0800 Subject: [PATCH] 0228.5 --- src/app/api/sun/route.ts | 60 +++++++++++++++++++++++ src/app/api/weather/route.ts | 81 +++++++++++++++++++++---------- src/app/page.tsx | 25 ++++++++-- src/components/WeatherSection.tsx | 73 ++++++++++++++-------------- src/types/magic-mirror.ts | 10 ++++ 5 files changed, 184 insertions(+), 65 deletions(-) create mode 100644 src/app/api/sun/route.ts diff --git a/src/app/api/sun/route.ts b/src/app/api/sun/route.ts new file mode 100644 index 0000000..c8acbde --- /dev/null +++ b/src/app/api/sun/route.ts @@ -0,0 +1,60 @@ +import { NextResponse } from 'next/server'; + +export async function GET() { + const apiKey = process.env.QWEATHER_API_KEY; + // 使用北京坐标(示例) + const [longitude, latitude] = [116.3974, 39.9093]; + const today = new Date(); + + try { + // 获取未来6天日出日落数据 + const datePromises = Array.from({ length: 6 }).map((_, i) => { + const date = new Date(today); + date.setDate(today.getDate() + i); + return date.toISOString().split('T')[0].replace(/-/g, ''); // 格式化为yyyyMMdd + }); + + const responses = await Promise.all( + datePromises.map(date => + fetch(`https://devapi.qweather.com/v7/astronomy/sun?location=${longitude},${latitude}&date=${date}&key=${apiKey}`) + ) + ); + + const sunData = await Promise.all(responses.map(res => res.json())); + + // 格式化数据 + const formattedData = sunData.map((item, index) => ({ + date: new Date(today.getTime() + index * 86400000).toISOString().split('T')[0], + sunrise: formatSunTime(item.sunrise), + sunset: formatSunTime(item.sunset) + })); + + return NextResponse.json(formattedData, { + headers: { + 'Cache-Control': 'public, s-maxage=3600', + 'CDN-Cache-Control': 'public, s-maxage=7200' + } + }); + + } catch (error) { + console.error('日出日落数据获取失败:', error); + return NextResponse.json( + { error: "日出日落数据获取失败" }, + { status: 500 } + ); + } +} + +// 时间格式化函数 +function formatSunTime(isoTime: string) { + try { + const date = new Date(isoTime); + return date.toLocaleTimeString('zh-CN', { + hour: '2-digit', + minute: '2-digit', + hour12: false + }); + } catch { + return isoTime.split('T')[1]?.substring(0, 5) || '--:--'; + } +} \ No newline at end of file diff --git a/src/app/api/weather/route.ts b/src/app/api/weather/route.ts index ecd6cd2..a601f02 100644 --- a/src/app/api/weather/route.ts +++ b/src/app/api/weather/route.ts @@ -3,32 +3,52 @@ import { NextResponse } from 'next/server'; export async function GET() { const apiKey = process.env.CAIYUN_API_KEY; // 请替换为你的实际坐标(示例使用北京坐标) - const apiUrl = `https://api.caiyunapp.com/v2.6/${apiKey}/116.3974,39.9093/weather?alert=true&dailysteps=5`; - + const [longitude, latitude] = [116.3974, 39.9093]; + try { - const response = await fetch(apiUrl, { next: { revalidate: 600 } }); - const data = await response.json(); - + // 同时获取实时天气和6天预报 + const [realtimeRes, dailyRes] = await Promise.all([ + fetch(`https://api.caiyunapp.com/v2.6/${apiKey}/${longitude},${latitude}/realtime`), + fetch(`https://api.caiyunapp.com/v2.6/${apiKey}/${longitude},${latitude}/daily?dailysteps=6`) + ]); + + // 检查响应状态 + if (!realtimeRes.ok || !dailyRes.ok) { + throw new Error('API请求失败'); + } + + const realtimeData = await realtimeRes.json(); + const dailyData = await dailyRes.json(); + + // 检查API返回状态 + if (realtimeData.status !== 'ok' || dailyData.status !== 'ok') { + throw new Error('天气数据异常'); + } + // 数据格式转换 const formattedData = { - temp: Math.round(data.result.realtime.temperature), - feelsLike: Math.round(data.result.realtime.apparent_temperature), - condition: translateSkycon(data.result.realtime.skycon), - sunrise: data.result.daily.astro[0].sunrise.time, - sunset: data.result.daily.astro[0].sunset.time, - windSpeed: (data.result.realtime.wind.speed * 3.6).toFixed(1), // 转换为km/h - windDirection: getWindDirection(data.result.realtime.wind.direction), - uvIndex: data.result.realtime.life_index.ultraviolet.desc, - forecast: data.result.daily.temperature.map((item: any, index: number) => ({ + temp: Math.round(realtimeData.result.realtime.temperature), + feelsLike: Math.round(realtimeData.result.realtime.apparent_temperature), + condition: translateSkycon(realtimeData.result.realtime.skycon), + windSpeed: realtimeData.result.realtime.wind.speed.toFixed(1), + windDirection: getWindDirection(realtimeData.result.realtime.wind.direction), + uvIndex: realtimeData.result.realtime.life_index.ultraviolet.desc, + forecast: dailyData.result.daily.temperature.slice(0, 6).map((item: any, index: number) => ({ day: formatDailyDate(item.date), low: Math.round(item.min), high: Math.round(item.max), - condition: translateSkycon(data.result.daily.skycon[index].value) + condition: translateSkycon(dailyData.result.daily.skycon[index]?.value) })) }; - return NextResponse.json(formattedData); + return NextResponse.json(formattedData, { + headers: { + 'Cache-Control': 'public, s-maxage=600', // 缓存10分钟 + 'CDN-Cache-Control': 'public, s-maxage=1800' // CDN缓存30分钟 + } + }); } catch (error) { + console.error('天气获取失败:', error); return NextResponse.json( { error: "天气数据获取失败" }, { status: 500 } @@ -36,7 +56,7 @@ export async function GET() { } } -// 天气状况翻译 +// 天气状况翻译(根据最新文档更新) function translateSkycon(skycon: string) { const skyconMap: Record = { "CLEAR_DAY": "晴", @@ -48,20 +68,31 @@ function translateSkycon(skycon: string) { "MODERATE_RAIN": "中雨", "HEAVY_RAIN": "大雨", "STORM_RAIN": "暴雨", - // 其他天气代码可继续补充... + "FOG": "雾", + "LIGHT_SNOW": "小雪", + "MODERATE_SNOW": "中雪", + "HEAVY_SNOW": "大雪", + "STORM_SNOW": "暴雪", + "DUST": "浮尘", + "SAND": "沙尘", + "WIND": "大风" }; - return skyconMap[skycon] || skycon; + return skyconMap[skycon] || "未知天气"; } -// 风向转换 +// 风向转换(根据官方文档说明) function getWindDirection(degree: number) { const directions = ["北风", "东北风", "东风", "东南风", "南风", "西南风", "西风", "西北风"]; - return directions[Math.round(degree % 360 / 45) % 8]; + return directions[Math.floor((degree + 22.5) % 360 / 45)] || "未知风向"; } -// 日期格式化 +// 日期格式化(显示星期) function formatDailyDate(dateStr: string) { - const date = new Date(dateStr); - const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"]; - return weekdays[date.getDay()]; + try { + const date = new Date(dateStr); + const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"]; + return weekdays[date.getDay()]; + } catch { + return "未知日期"; + } } \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 0a55cfc..d33cf59 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -11,28 +11,44 @@ import CalendarGrid from "../components/CalendarGrid"; import WeatherSection from "../components/WeatherSection"; import NewsSection from "../components/NewsSection"; import { generateCalendarDays } from "@/utils/calendar"; -import { WeatherData, NewsItem } from "@/types/magic-mirror"; +import { WeatherData, NewsItem, SunData } from "@/types/magic-mirror"; import VoiceAssistant from "@/components/VoiceAssistant"; import useSWR from "swr"; +// 定义通用数据获取器 +const fetcher = (url: string) => fetch(url).then((res) => res.json()); const MagicMirror = () => { const [time, setTime] = useState(new Date()); const calendarDays = useMemo(() => generateCalendarDays(), []); // 天气示例数据 - // 替换原有的weatherData定义部分 const { data: weatherData, error: weatherError } = useSWR( "/api/weather", (url: string) => fetch(url).then((res) => res.json()) ); // 新闻数据 - // 替换原有的newsItems定义部分: const { data: newsItems = [], error: newsError } = useSWR( "/api/news", (url: string) => fetch(url).then((res) => res.json()) ); + // 在组件顶部新增SWR请求 + // 日出日落数据 + const { data: sunData = [] } = useSWR("/api/sun", fetcher); + + // 合并天气数据和日出日落数据 + const mergedWeatherData = useMemo(() => { + if (weatherData && sunData.length > 0) { + return { + ...weatherData, + sunrise: sunData[0]?.sunrise || "06:00", + sunset: sunData[0]?.sunset || "18:00", + }; + } + return weatherData; + }, [weatherData, sunData]); + // 时间更新 useEffect(() => { const timer = setInterval(() => setTime(new Date()), 1000); @@ -102,7 +118,8 @@ const MagicMirror = () => { - + {/**/} + diff --git a/src/components/WeatherSection.tsx b/src/components/WeatherSection.tsx index 8dab77b..760bbe7 100644 --- a/src/components/WeatherSection.tsx +++ b/src/components/WeatherSection.tsx @@ -8,11 +8,10 @@ import { FC } from "react"; import { WeatherData } from "../types/magic-mirror"; -import WbSunnyIcon from "@mui/icons-material/WbSunny"; import CircularProgress from "@mui/material/CircularProgress"; interface WeatherSectionProps { - data?: WeatherData; // 允许undefined状态(加载中) + data?: WeatherData; } const WeatherSection: FC = ({ data }) => { @@ -26,8 +25,9 @@ const WeatherSection: FC = ({ data }) => { ); } - // 日出日落时间格式化(去除日期部分) - const formatTime = (isoTime: string) => { + // 日出日落时间格式化(安全访问) + const formatTime = (isoTime?: string) => { + if (!isoTime) return "--:--"; try { return new Date(isoTime).toLocaleTimeString("zh-CN", { hour: "2-digit", @@ -35,12 +35,12 @@ const WeatherSection: FC = ({ data }) => { hour12: false, }); } catch { - return isoTime.split("T")[1]?.slice(0, 5) || isoTime; // 回退处理 + return isoTime.split("T")[1]?.slice(0, 5) || "--:--"; } }; - // 获取天气图标 - const getWeatherIcon = (condition: string) => { + // 获取天气图标(带默认值) + const getWeatherIcon = (condition?: string) => { const iconMap: Record = { 晴: "☀️", 多云: "⛅", @@ -49,21 +49,22 @@ const WeatherSection: FC = ({ data }) => { 中雨: "🌧️", 大雨: "⛈️", 暴雨: "🌧️💦", - // 其他天气类型可继续扩展... }; - return iconMap[condition] || "🌤️"; + return condition ? iconMap[condition] || "🌤️" : "🌤️"; }; + // 安全获取天气预报数据 + const forecastData = data.forecast?.slice(0, 5) || []; + return ( <> {/* 当前天气模块 */}
- {/* 风速和日出日落信息 */}
风速
- {data.windSpeed}km/h {data.windDirection} + {data.windSpeed || "--"}km/h {data.windDirection || ""}
@@ -73,42 +74,42 @@ const WeatherSection: FC = ({ data }) => {
- - {/* 温度显示区域 */}
{getWeatherIcon(data.condition)}
-
{data.temp}°C
-
体感 {data.feelsLike}°C
+
{data.temp ?? "--"}°C
+
+ 体感 {data.feelsLike ?? "--"}°C +
- - {/* 紫外线指数 */}
紫外线指数
-
{data.uvIndex}
+
{data.uvIndex || "--"}
- {/* 天气预报模块 */} -
- {data.forecast.slice(0, 5).map((day, index) => ( -
-
{day.day}
-
- {getWeatherIcon(day.condition)} + {/* 天气预报模块(安全渲染) */} + {forecastData.length > 0 && ( +
+ {forecastData.map((day, index) => ( +
+
{day.day || "未知"}
+
+ {getWeatherIcon(day.condition)} +
+
+ {day.low ?? "--"}° + {day.high ?? "--"}° +
-
- {day.low}° - {day.high}° -
-
- ))} -
+ ))} +
+ )} ); }; diff --git a/src/types/magic-mirror.ts b/src/types/magic-mirror.ts index 153910f..ff6fbf9 100644 --- a/src/types/magic-mirror.ts +++ b/src/types/magic-mirror.ts @@ -3,6 +3,7 @@ * 包含天气、新闻、日历等数据结构的类型定义 */ +// 新增天气数据类型定义 export interface WeatherData { temp: number; feelsLike: number; @@ -20,6 +21,14 @@ export interface WeatherData { }[]; } +// 新增日出日落数据类型定义 +export interface SunData { + date: string; + sunrise: string; + sunset: string; +} + +// 新增新闻数据类型定义 export interface NewsItem { uniquekey: string; title: string; @@ -31,6 +40,7 @@ export interface NewsItem { is_content: string; } +// 新增日历数据类型定义 export type CalendarDay = { day: number; isCurrent: boolean;