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*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
#.env*
|
||||||
|
.env.local
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
@ -13,7 +13,7 @@ COPY package.json ./
|
|||||||
# 使用国内镜像源(如果网络连接较慢)
|
# 使用国内镜像源(如果网络连接较慢)
|
||||||
#RUN pnpm config set registry https://registry.npm.taobao.org
|
#RUN pnpm config set registry https://registry.npm.taobao.org
|
||||||
# 安装所有依赖(包括开发依赖)
|
# 安装所有依赖(包括开发依赖)
|
||||||
RUN pnpm install --production=false
|
# RUN pnpm install --production=false
|
||||||
|
|
||||||
# 安装项目依赖
|
# 安装项目依赖
|
||||||
#RUN pnpm install
|
#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 calendarDays = useMemo(() => generateCalendarDays(), []);
|
||||||
|
|
||||||
// 天气示例数据
|
// 天气示例数据
|
||||||
const weatherData: WeatherData = {
|
// 替换原有的weatherData定义部分
|
||||||
temp: 24,
|
const { data: weatherData, error: weatherError } = useSWR<WeatherData>(
|
||||||
feelsLike: 26,
|
"/api/weather",
|
||||||
condition: "晴",
|
(url: string) => fetch(url).then((res) => res.json())
|
||||||
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: "🌧️" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新闻数据
|
// 新闻数据
|
||||||
// 替换原有的newsItems定义部分:
|
// 替换原有的newsItems定义部分:
|
||||||
|
@ -3,21 +3,62 @@
|
|||||||
/**
|
/**
|
||||||
* 天气信息组件
|
* 天气信息组件
|
||||||
* 包含当前天气和天气预报展示
|
* 包含当前天气和天气预报展示
|
||||||
|
* 支持加载状态处理和API数据格式转换
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { WeatherData } from "../types/magic-mirror";
|
import { WeatherData } from "../types/magic-mirror";
|
||||||
import WbSunnyIcon from "@mui/icons-material/WbSunny";
|
import WbSunnyIcon from "@mui/icons-material/WbSunny";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
|
||||||
interface WeatherSectionProps {
|
interface WeatherSectionProps {
|
||||||
data: WeatherData;
|
data?: WeatherData; // 允许undefined状态(加载中)
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
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 (
|
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">
|
||||||
<div className="text-gray-400">风速</div>
|
<div className="text-gray-400">风速</div>
|
||||||
@ -28,19 +69,21 @@ 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">
|
||||||
{data.sunrise} / {data.sunset}
|
{formatTime(data.sunrise)} / {formatTime(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">☀️</div>
|
<div className="text-4xl">{getWeatherIcon(data.condition)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-3xl">{data.temp}°C</div>
|
<div className="text-3xl">{data.temp}°C</div>
|
||||||
<div className="text-gray-400 text-sm">体感 {data.feelsLike}°C</div>
|
<div className="text-gray-400 text-sm">体感 {data.feelsLike}°C</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>
|
||||||
@ -49,16 +92,19 @@ const WeatherSection: FC<WeatherSectionProps> = ({ data }) => {
|
|||||||
|
|
||||||
{/* 天气预报模块 */}
|
{/* 天气预报模块 */}
|
||||||
<div className="absolute top-64 right-8 w-64 space-y-2">
|
<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
|
<div
|
||||||
key={day.day}
|
key={day.day}
|
||||||
className="flex justify-between text-sm"
|
className="flex justify-between items-center text-sm group"
|
||||||
style={{ opacity: 1 - index * 0.15 }}
|
style={{ opacity: 1 - index * 0.15 }}
|
||||||
>
|
>
|
||||||
<div className="text-gray-300">{day.day}</div>
|
<div className="text-gray-300 w-12">{day.day}</div>
|
||||||
<div className="text-gray-400">{day.condition}</div>
|
<div className="text-gray-400 transition-opacity opacity-70 group-hover:opacity-100">
|
||||||
<div className="text-gray-300">
|
{getWeatherIcon(day.condition)}
|
||||||
{day.low}° {day.high}°
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<span className="text-blue-300">{day.low}°</span>
|
||||||
|
<span className="text-red-300">{day.high}°</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
* 包含天气、新闻、日历等数据结构的类型定义
|
* 包含天气、新闻、日历等数据结构的类型定义
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type WeatherData = {
|
export interface WeatherData {
|
||||||
temp: number;
|
temp: number;
|
||||||
feelsLike: number;
|
feelsLike: number;
|
||||||
condition: string;
|
condition: string;
|
||||||
sunrise: string;
|
sunrise: string;
|
||||||
sunset: string;
|
sunset: string;
|
||||||
windSpeed: number;
|
windSpeed: string;
|
||||||
windDirection: string;
|
windDirection: string;
|
||||||
uvIndex: string;
|
uvIndex: string;
|
||||||
forecast: {
|
forecast: {
|
||||||
@ -18,7 +18,7 @@ export type WeatherData = {
|
|||||||
high: number;
|
high: number;
|
||||||
condition: string;
|
condition: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface NewsItem {
|
export interface NewsItem {
|
||||||
uniquekey: string;
|
uniquekey: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user