Files
Train/apps/web/src/pages/HomePage.tsx
T
2026-06-16 00:55:20 +08:00

127 lines
4.5 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 { Link } from 'react-router-dom';
import { api } from '../api/client';
import type { ModelListItem } from '../types';
import {
computeSpeedEvolution,
ERAS,
pickRepresentatives,
} from '../lib/eras';
import { Thumb } from '../components/Thumb';
import { CategoryIcon, IconForward } from '../components/icons';
import { fmtEra, fmtValueUnit } from '../lib/format';
import { EvolutionCurve } from '../components/EvolutionCurve';
export function HomePage() {
const [models, setModels] = useState<ModelListItem[]>([]);
useEffect(() => {
api.models({ pageSize: 1000 }).then((d) => setModels(d.items)).catch(() => {});
}, []);
const evo = computeSpeedEvolution(models);
const total = models.length;
const years = models
.map((m) => m.first_year)
.filter((y): y is number => typeof y === 'number');
const minYear = years.length ? Math.min(...years) : 1881;
const topSpeed = Math.max(0, ...models.map((m) => m.max_speed_value ?? 0));
return (
<div className="home">
{/* 英雄区 + 时速演进:并排一行 */}
<section className="hero-row">
<div className="hero">
<h1></h1>
<p className="hero-sub">
线
<strong></strong>
</p>
<div className="hero-stats">
<div>
<b>{total || '—'}</b>
<span></span>
</div>
<div>
<b>{minYear}</b>
<span></span>
</div>
<div>
<b>{topSpeed || '—'}</b>
<span> km/h</span>
</div>
<div>
<b>4</b>
<span></span>
</div>
</div>
<Link to="/explore" className="hero-cta">
<IconForward size={16} />
</Link>
</div>
<div className="hero-evo">
<h2></h2>
<p className="muted eras-intro">
线
</p>
{evo.length >= 2 && <EvolutionCurve points={evo} />}
</div>
</section>
{/* 主线:四个时代章节 */}
<section className="eras">
<h2>沿</h2>
<div className="era-rail">
{ERAS.map((era, i) => {
const reps = pickRepresentatives(models, era, 4);
return (
<article
className="era"
key={era.key}
style={{ ['--accent' as string]: era.accent }}
>
<div className="era-head">
<span className="era-icon">
<CategoryIcon category={era.categories[0]} size={26} />
</span>
<div>
<h3>
<span className="era-step">{i + 1}</span>
{era.title}
</h3>
<span className="era-period">{era.period}</span>
</div>
</div>
<p className="era-blurb">{era.blurb}</p>
<div className="era-reps">
{reps.map((m) => (
<Link key={m.id} to={`/models/${m.id}`} className="mini">
{m.cover_url ? (
<img src={m.cover_url} alt={m.model_code} loading="lazy" />
) : (
<Thumb category={m.category} code={m.model_code} size="sm" />
)}
<span className="mini-code">{m.model_code}</span>
<span className="mini-meta">
{fmtValueUnit(m.max_speed_value, m.max_speed_unit)} ·{' '}
{fmtEra(m.first_year, m.last_year)}
</span>
</Link>
))}
</div>
<Link
to={`/explore?category=${encodeURIComponent(era.categories[0])}`}
className="era-more"
>
{era.title.replace('时代', '')} <IconForward size={14} />
</Link>
</article>
);
})}
</div>
</section>
</div>
);
}