SystemApplication/Client/src/components/start/Greeting.tsx
cc a185e56c22 feat: 导航起始页 + 天气/定位 + 天气动态效果
起始页(StartPage)
- 极简玻璃风导航页作未登录首页:时段问候、Bing 默认可切换搜索、10 个快捷位、右下齿轮进登录
- 昼夜背景自动切换(light/dark),按天气图标码 + 本地时间判断
- 天气动态效果(WeatherFx canvas):雨/雪/雷,克制风格,尊重 reduced-motion

天气与定位(前后端)
- 后端 /api/web/weather:高德 IP 定位(城市+坐标) + 和风实时天气
- 和风用 JWT(EdDSA/Ed25519)认证,密钥走环境变量、仅后端持有
- 每日限流(高德 300、和风 900,北京 0 点归零) + 10 分钟按 IP 缓存
- 前端 useWeather + WeatherIcon(7 类极简 SVG)

导航与登录
- 浏览器返回键支持(History API);返回即登出
- 记住我:勾选存 localStorage、不勾存 sessionStorage,默认不勾

其他
- .gitignore 忽略 *.pem/*.env 等密钥
- 新增文档 docs/startpage-weather-location.md 及部署 spec/plan

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:13:18 +08:00

75 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react'
import type { WeatherState } from '../../hooks/useWeather'
import { WeatherIcon } from './WeatherIcon'
// 按本地小时给出 早/中/晚/夜 问候(默认英文,改中文只需替换下面的文案)。
function greetingFor(hour: number): string {
if (hour >= 5 && hour < 12) return 'Good morning'
if (hour >= 12 && hour < 18) return 'Good afternoon'
if (hour >= 18 && hour < 23) return 'Good evening'
return 'Good night'
}
// 天气行:根据后端返回的业务态分别渲染。
function WeatherLine({ wx }: { wx: WeatherState }) {
let content = null
if (!wx.loading && wx.data) {
const d = wx.data
if (d.ok) {
content = (
<>
<WeatherIcon code={d.icon} size={18} />
<span>
{d.city} · {d.text} · {d.temp}°
</span>
</>
)
} else if (d.reason === 'quota_exceeded') {
// “表个态”:当日额度用完。
content = <span className="text-white/55">, 0 </span>
}
// not_configured / failed静默不打扰页面
}
// 固定最小高度,避免天气加载完成后页面跳动。
return (
<div className="mt-3 flex min-h-[1.5rem] items-center justify-center gap-2 font-sans text-sm text-white/70">
{content}
</div>
)
}
export function Greeting({ wx }: { wx: WeatherState }) {
const [now, setNow] = useState(() => new Date())
useEffect(() => {
// 每 30s 刷新,足够让分钟与跨时段问候保持准确。
const id = setInterval(() => setNow(new Date()), 30_000)
return () => clearInterval(id)
}, [])
const greeting = greetingFor(now.getHours())
const time = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
const date = now.toLocaleDateString([], {
weekday: 'long',
month: 'long',
day: 'numeric',
})
return (
<div className="text-center text-white [text-shadow:0_1px_16px_rgba(0,0,0,0.45)]">
<p className="font-sans text-xs uppercase tracking-[0.4em] text-white/65">
{time} · {date}
</p>
<h1 className="mt-4 font-display text-5xl font-light italic leading-tight sm:text-6xl">
{greeting}
<span className="text-white/45">.</span>
</h1>
<WeatherLine wx={wx} />
</div>
)
}