This commit is contained in:
parent
2521db74a9
commit
012715ee81
@ -1,33 +1,48 @@
|
|||||||
|
// src/app/api/sun/route.ts
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const apiKey = process.env.QWEATHER_API_KEY;
|
const apiKey = process.env.QWEATHER_API_KEY;
|
||||||
// 使用北京坐标(示例)
|
|
||||||
const [longitude, latitude] = [116.3974, 39.9093];
|
const [longitude, latitude] = [116.3974, 39.9093];
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取未来6天日出日落数据
|
// 生成包含今天在内的未来6天日期
|
||||||
const datePromises = Array.from({ length: 6 }).map((_, i) => {
|
const datePromises = Array.from({ length: 6 }).map((_, i) => {
|
||||||
const date = new Date(today);
|
const date = new Date(today);
|
||||||
date.setDate(today.getDate() + i);
|
date.setDate(today.getDate() + i);
|
||||||
return date.toISOString().split('T')[0].replace(/-/g, ''); // 格式化为yyyyMMdd
|
return date;
|
||||||
});
|
});
|
||||||
|
|
||||||
const responses = await Promise.all(
|
const responses = await Promise.all(
|
||||||
datePromises.map(date =>
|
datePromises.map(date => {
|
||||||
fetch(`https://devapi.qweather.com/v7/astronomy/sun?location=${longitude},${latitude}&date=${date}&key=${apiKey}`)
|
const formattedDate = date.toISOString().split('T')[0].replace(/-/g, '');
|
||||||
)
|
return fetch(
|
||||||
|
`https://devapi.qweather.com/v7/astronomy/sun?` +
|
||||||
|
`location=${longitude},${latitude}&date=${formattedDate}&key=${apiKey}`
|
||||||
|
);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 增强的错误处理
|
||||||
|
const errors = responses.filter(res => !res.ok);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`API请求失败: ${errors.map(e => e.statusText).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
const sunData = await Promise.all(responses.map(res => res.json()));
|
const sunData = await Promise.all(responses.map(res => res.json()));
|
||||||
|
|
||||||
// 格式化数据
|
// 数据格式化和验证
|
||||||
const formattedData = sunData.map((item, index) => ({
|
const formattedData = sunData.map((item, index) => {
|
||||||
date: new Date(today.getTime() + index * 86400000).toISOString().split('T')[0],
|
const targetDate = new Date(today);
|
||||||
sunrise: formatSunTime(item.sunrise),
|
targetDate.setDate(today.getDate() + index);
|
||||||
sunset: formatSunTime(item.sunset)
|
|
||||||
}));
|
return {
|
||||||
|
date: targetDate.toISOString().split('T')[0],
|
||||||
|
sunrise: safeFormatTime(item.sunrise),
|
||||||
|
sunset: safeFormatTime(item.sunset)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json(formattedData, {
|
return NextResponse.json(formattedData, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -39,22 +54,28 @@ export async function GET() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('日出日落数据获取失败:', error);
|
console.error('日出日落数据获取失败:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "日出日落数据获取失败" },
|
{ error: "数据获取失败,请稍后重试" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间格式化函数
|
// 健壮的时间格式化方法
|
||||||
function formatSunTime(isoTime: string) {
|
function safeFormatTime(timeStr: string) {
|
||||||
|
// 直接匹配HH:mm格式
|
||||||
|
const directMatch = timeStr.match(/(\d{2}:\d{2})/);
|
||||||
|
if (directMatch) return directMatch[1];
|
||||||
|
|
||||||
|
// 处理ISO格式
|
||||||
try {
|
try {
|
||||||
const date = new Date(isoTime);
|
const date = new Date(timeStr);
|
||||||
return date.toLocaleTimeString('zh-CN', {
|
return date.toLocaleTimeString('zh-CN', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
hour12: false
|
hour12: false
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return isoTime.split('T')[1]?.substring(0, 5) || '--:--';
|
// 最终处理非常规格式
|
||||||
|
return timeStr.split('T')[1]?.substring(0, 5) || '--:--';
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -77,7 +77,7 @@ function translateSkycon(skycon: string) {
|
|||||||
"SAND": "沙尘",
|
"SAND": "沙尘",
|
||||||
"WIND": "大风"
|
"WIND": "大风"
|
||||||
};
|
};
|
||||||
return skyconMap[skycon] || "未知天气";
|
return skyconMap[skycon] || "未知";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 风向转换(根据官方文档说明)
|
// 风向转换(根据官方文档说明)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 魔镜主页面
|
* 魔镜主页面src\app\page.tsx
|
||||||
* 集成所有子组件并管理主要状态
|
* 集成所有子组件并管理主要状态
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
"use client";
|
// src/components/WeatherSection.tsx
|
||||||
|
|
||||||
/**
|
|
||||||
* 天气信息组件
|
|
||||||
* 包含当前天气和天气预报展示
|
|
||||||
* 支持加载状态处理和API数据格式转换
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { WeatherData } from "../types/magic-mirror";
|
import { WeatherData } from "../types/magic-mirror";
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
@ -15,7 +8,6 @@ interface WeatherSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
||||||
// 处理加载状态
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-8 right-0 w-64 flex items-center justify-center h-32">
|
<div className="absolute top-8 right-0 w-64 flex items-center justify-center h-32">
|
||||||
@ -25,21 +17,7 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日出日落时间格式化(安全访问)
|
// 获取天气图标
|
||||||
const formatTime = (isoTime?: string) => {
|
|
||||||
if (!isoTime) return "--:--";
|
|
||||||
try {
|
|
||||||
return new Date(isoTime).toLocaleTimeString("zh-CN", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: false,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return isoTime.split("T")[1]?.slice(0, 5) || "--:--";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取天气图标(带默认值)
|
|
||||||
const getWeatherIcon = (condition?: string) => {
|
const getWeatherIcon = (condition?: string) => {
|
||||||
const iconMap: Record<string, string> = {
|
const iconMap: Record<string, string> = {
|
||||||
晴: "☀️",
|
晴: "☀️",
|
||||||
@ -53,12 +31,8 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
|||||||
return condition ? iconMap[condition] || "🌤️" : "🌤️";
|
return condition ? iconMap[condition] || "🌤️" : "🌤️";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 安全获取天气预报数据
|
|
||||||
const forecastData = data.forecast?.slice(0, 5) || [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 当前天气模块 */}
|
|
||||||
<div className="absolute top-8 right-0 w-64 space-y-4">
|
<div className="absolute top-8 right-0 w-64 space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@ -70,10 +44,11 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-gray-400">日出/日落</div>
|
<div className="text-gray-400">日出/日落</div>
|
||||||
<div className="text-gray-300">
|
<div className="text-gray-300">
|
||||||
{formatTime(data.sunrise)} / {formatTime(data.sunset)}
|
{data.sunrise || "--:--"} / {data.sunset || "--:--"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-4xl">{getWeatherIcon(data.condition)}</div>
|
<div className="text-4xl">{getWeatherIcon(data.condition)}</div>
|
||||||
<div>
|
<div>
|
||||||
@ -83,33 +58,63 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-gray-400 text-sm">紫外线指数</div>
|
<div className="text-gray-400 text-sm">紫外线指数</div>
|
||||||
<div className="text-gray-300">{data.uvIndex || "--"}</div>
|
<div className="text-gray-300">{data.uvIndex || "--"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 天气预报模块(安全渲染) */}
|
{/* 天气预报模块 */}
|
||||||
{forecastData.length > 0 && (
|
<div className="absolute top-64 right-8 w-72 bg-black/50 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||||
<div className="absolute top-64 right-8 w-64 space-y-2">
|
<div className="text-gray-300 text-sm font-medium mb-3">
|
||||||
{forecastData.map((day, index) => (
|
未来5天预报
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{data.forecast.slice(0, 5).map((day, index) => (
|
||||||
<div
|
<div
|
||||||
key={day.day || index}
|
key={day.day || index}
|
||||||
className="flex justify-between items-center text-sm group"
|
className="flex justify-between items-center group transition-all duration-200 hover:bg-white/10 px-2 py-1 rounded-md"
|
||||||
style={{ opacity: 1 - index * 0.15 }}
|
style={{ opacity: 1 - index * 0.15 }}
|
||||||
>
|
>
|
||||||
<div className="text-gray-300 w-12">{day.day || "未知"}</div>
|
{/* 日期 */}
|
||||||
<div className="text-gray-400 transition-opacity opacity-70 group-hover:opacity-100">
|
<div className="text-gray-300 text-sm font-light w-16">
|
||||||
{getWeatherIcon(day.condition)}
|
{day.day || "未知"}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="text-blue-300">{day.low ?? "--"}°</span>
|
{/* 天气图标
|
||||||
<span className="text-red-300">{day.high ?? "--"}°</span>
|
<div className="text-gray-400 text-lg">
|
||||||
|
{getWeatherIcon(day.condition)}
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* 天气图标和名称 */}
|
||||||
|
<div className="flex items-center gap-2 w-24">
|
||||||
|
<div className="text-gray-400 text-lg">
|
||||||
|
{getWeatherIcon(day.condition)}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-300 text-sm font-light">
|
||||||
|
{day.condition || "未知"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 温度范围 */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-blue-300 text-sm">
|
||||||
|
{day.low ?? "--"}°
|
||||||
|
</span>
|
||||||
|
<div className="w-12 h-1 bg-gradient-to-r from-blue-400 to-red-400 rounded-full" />
|
||||||
|
<span className="text-red-300 text-sm">
|
||||||
|
{day.high ?? "--"}°
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user