This commit is contained in:
parent
3d83a2ac75
commit
f5dc9a4843
5
.env
Normal file
5
.env
Normal file
@ -0,0 +1,5 @@
|
||||
# 聚合数据API
|
||||
JUHE_NEWS_KEY=edbc3b96f022b59141961e2137f69b4a
|
||||
|
||||
# 彩云天气API
|
||||
CAIYUN_API_KEY=TAkhjf8d1nlSlspN
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,7 +31,8 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
#.env*
|
||||
.env.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
@ -13,7 +13,7 @@ COPY package.json ./
|
||||
# 使用国内镜像源(如果网络连接较慢)
|
||||
#RUN pnpm config set registry https://registry.npm.taobao.org
|
||||
# 安装所有依赖(包括开发依赖)
|
||||
RUN pnpm install --production=false
|
||||
# RUN pnpm install --production=false
|
||||
|
||||
# 安装项目依赖
|
||||
#RUN pnpm install
|
||||
|
67
src/app/api/weather/route.ts
Normal file
67
src/app/api/weather/route.ts
Normal file
@ -0,0 +1,67 @@
|
||||
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`;
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, { next: { revalidate: 600 } });
|
||||
const data = await response.json();
|
||||
|
||||
// 数据格式转换
|
||||
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) => ({
|
||||
day: formatDailyDate(item.date),
|
||||
low: Math.round(item.min),
|
||||
high: Math.round(item.max),
|
||||
condition: translateSkycon(data.result.daily.skycon[index].value)
|
||||
}))
|
||||
};
|
||||
|
||||
return NextResponse.json(formattedData);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: "天气数据获取失败" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 天气状况翻译
|
||||
function translateSkycon(skycon: string) {
|
||||
const skyconMap: Record<string, string> = {
|
||||
"CLEAR_DAY": "晴",
|
||||
"CLEAR_NIGHT": "晴",
|
||||
"PARTLY_CLOUDY_DAY": "多云",
|
||||
"PARTLY_CLOUDY_NIGHT": "多云",
|
||||
"CLOUDY": "阴",
|
||||
"LIGHT_RAIN": "小雨",
|
||||
"MODERATE_RAIN": "中雨",
|
||||
"HEAVY_RAIN": "大雨",
|
||||
"STORM_RAIN": "暴雨",
|
||||
// 其他天气代码可继续补充...
|
||||
};
|
||||
return skyconMap[skycon] || skycon;
|
||||
}
|
||||
|
||||
// 风向转换
|
||||
function getWindDirection(degree: number) {
|
||||
const directions = ["北风", "东北风", "东风", "东南风", "南风", "西南风", "西风", "西北风"];
|
||||
return directions[Math.round(degree % 360 / 45) % 8];
|
||||
}
|
||||
|
||||
// 日期格式化
|
||||
function formatDailyDate(dateStr: string) {
|
||||
const date = new Date(dateStr);
|
||||
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
||||
return weekdays[date.getDay()];
|
||||
}
|
@ -20,23 +20,11 @@ const MagicMirror = () => {
|
||||
const calendarDays = useMemo(() => generateCalendarDays(), []);
|
||||
|
||||
// 天气示例数据
|
||||
const weatherData: WeatherData = {
|
||||
temp: 24,
|
||||
feelsLike: 26,
|
||||
condition: "晴",
|
||||
sunrise: "06:12",
|
||||
sunset: "18:34",
|
||||
windSpeed: 5,
|
||||
windDirection: "东南风",
|
||||
uvIndex: "中等",
|
||||
forecast: [
|
||||
{ day: "周二", low: 18, high: 26, condition: "☀️" },
|
||||
{ day: "周三", low: 20, high: 28, condition: "⛅" },
|
||||
{ day: "周四", low: 19, high: 27, condition: "🌤️" },
|
||||
{ day: "周五", low: 17, high: 25, condition: "☀️" },
|
||||
{ day: "周六", low: 16, high: 24, condition: "🌧️" },
|
||||
],
|
||||
};
|
||||
// 替换原有的weatherData定义部分
|
||||
const { data: weatherData, error: weatherError } = useSWR<WeatherData>(
|
||||
"/api/weather",
|
||||
(url: string) => fetch(url).then((res) => res.json())
|
||||
);
|
||||
|
||||
// 新闻数据
|
||||
// 替换原有的newsItems定义部分:
|
||||
|
@ -3,21 +3,62 @@
|
||||
/**
|
||||
* 天气信息组件
|
||||
* 包含当前天气和天气预报展示
|
||||
* 支持加载状态处理和API数据格式转换
|
||||
*/
|
||||
|
||||
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;
|
||||
data?: WeatherData; // 允许undefined状态(加载中)
|
||||
}
|
||||
|
||||
const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
||||
// 处理加载状态
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="absolute top-8 right-0 w-64 flex items-center justify-center h-32">
|
||||
<CircularProgress size={24} color="inherit" />
|
||||
<span className="ml-2 text-gray-400">天气加载中...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 日出日落时间格式化(去除日期部分)
|
||||
const formatTime = (isoTime: string) => {
|
||||
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) || isoTime; // 回退处理
|
||||
}
|
||||
};
|
||||
|
||||
// 获取天气图标
|
||||
const getWeatherIcon = (condition: string) => {
|
||||
const iconMap: Record<string, string> = {
|
||||
晴: "☀️",
|
||||
多云: "⛅",
|
||||
阴: "☁️",
|
||||
小雨: "🌧️",
|
||||
中雨: "🌧️",
|
||||
大雨: "⛈️",
|
||||
暴雨: "🌧️💦",
|
||||
// 其他天气类型可继续扩展...
|
||||
};
|
||||
return iconMap[condition] || "🌤️";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 当前天气模块 */}
|
||||
<div className="absolute top-8 right-0 w-64 space-y-4">
|
||||
{/* 风速和日出日落信息 */}
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-400">风速</div>
|
||||
@ -28,19 +69,21 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-400">日出/日落</div>
|
||||
<div className="text-gray-300">
|
||||
{data.sunrise} / {data.sunset}
|
||||
{formatTime(data.sunrise)} / {formatTime(data.sunset)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 温度显示区域 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-4xl">☀️</div>
|
||||
<div className="text-4xl">{getWeatherIcon(data.condition)}</div>
|
||||
<div>
|
||||
<div className="text-3xl">{data.temp}°C</div>
|
||||
<div className="text-gray-400 text-sm">体感 {data.feelsLike}°C</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 紫外线指数 */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-gray-400 text-sm">紫外线指数</div>
|
||||
<div className="text-gray-300">{data.uvIndex}</div>
|
||||
@ -49,16 +92,19 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
||||
|
||||
{/* 天气预报模块 */}
|
||||
<div className="absolute top-64 right-8 w-64 space-y-2">
|
||||
{data.forecast.map((day, index) => (
|
||||
{data.forecast.slice(0, 5).map((day, index) => (
|
||||
<div
|
||||
key={day.day}
|
||||
className="flex justify-between text-sm"
|
||||
className="flex justify-between items-center text-sm group"
|
||||
style={{ opacity: 1 - index * 0.15 }}
|
||||
>
|
||||
<div className="text-gray-300">{day.day}</div>
|
||||
<div className="text-gray-400">{day.condition}</div>
|
||||
<div className="text-gray-300">
|
||||
{day.low}° {day.high}°
|
||||
<div className="text-gray-300 w-12">{day.day}</div>
|
||||
<div className="text-gray-400 transition-opacity opacity-70 group-hover:opacity-100">
|
||||
{getWeatherIcon(day.condition)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="text-blue-300">{day.low}°</span>
|
||||
<span className="text-red-300">{day.high}°</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -3,13 +3,13 @@
|
||||
* 包含天气、新闻、日历等数据结构的类型定义
|
||||
*/
|
||||
|
||||
export type WeatherData = {
|
||||
export interface WeatherData {
|
||||
temp: number;
|
||||
feelsLike: number;
|
||||
condition: string;
|
||||
sunrise: string;
|
||||
sunset: string;
|
||||
windSpeed: number;
|
||||
windSpeed: string;
|
||||
windDirection: string;
|
||||
uvIndex: string;
|
||||
forecast: {
|
||||
@ -18,7 +18,7 @@ export type WeatherData = {
|
||||
high: number;
|
||||
condition: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface NewsItem {
|
||||
uniquekey: string;
|
||||
|
Loading…
Reference in New Issue
Block a user