diff --git a/src/app/[locale]/timeline/page.tsx b/src/app/[locale]/timeline/page.tsx index 6d52c6c..41947cb 100644 --- a/src/app/[locale]/timeline/page.tsx +++ b/src/app/[locale]/timeline/page.tsx @@ -1,7 +1,8 @@ import { getAIEvents } from '@/hooks/useAIEvents'; +import { TimelineSection } from '@/components/timeline/TimelineSection'; import { Metadata } from 'next'; -export const revalidate = 3600; // ISR 1小时 +export const revalidate = 3600; export async function generateMetadata({ params, @@ -13,25 +14,21 @@ export async function generateMetadata({ return { title: locale === 'zh' ? 'AI 发展时间轴' : 'AI Timeline', description: locale === 'zh' - ? '探索人工智能大语言模型的发展历程,从 2017 年 Transformer 到今天' - : 'Explore the evolution of AI large language models from 2017 Transformer to today', + ? '探索人工智能大语言模型的发展历程' + : 'Explore the evolution of AI large language models', }; } export default async function TimelinePage() { const events = await getAIEvents(); - // 按年份分组 const eventsByYear = events.reduce((acc, event) => { const year = new Date(event.eventDate).getFullYear(); - if (!acc[year]) { - acc[year] = []; - } + if (!acc[year]) acc[year] = []; acc[year].push(event); return acc; }, {} as Record); - // 按年份降序排序 const sortedYears = Object.keys(eventsByYear) .map(Number) .sort((a, b) => b - a); @@ -53,7 +50,6 @@ export default async function TimelinePage() { return (
- {/* Header */}

@@ -65,91 +61,14 @@ export default async function TimelinePage() {

- {/* Timeline */}
{sortedYears.map((year, index) => ( -
- {/* Year Label */} -
-
- - {year} - -
-
- - {/* Events Container */} -
-
- {eventsByYear[year].map((event, eventIndex) => ( -
- {/* Tape decoration */} -
- - {/* Image */} -
- {event.title} -
- - {/* Content */} -

- {event.title} -

-

- {event.description} -

- - {/* Date */} -
- {new Date(event.eventDate).toLocaleDateString('zh-CN')} -
- - {/* Source Link */} - {event.sourceUrl && ( - - 来源 → - - )} -
- ))} -
-
-
+ year={year} + events={eventsByYear[year]} + index={index} + /> ))}
diff --git a/src/components/timeline/EventCard.tsx b/src/components/timeline/EventCard.tsx new file mode 100644 index 0000000..29499ba --- /dev/null +++ b/src/components/timeline/EventCard.tsx @@ -0,0 +1,58 @@ +import { AIEvent } from '@prisma/client'; + +interface EventCardProps { + event: AIEvent; + index: number; +} + +export function EventCard({ event, index }: EventCardProps) { + const rotation = (Math.random() - 0.5) * 6; + const zIndex = Math.max(1, 50 - index * 10); + + return ( +
+ {/* Tape decoration */} +
+ + {/* Image */} +
+ {event.title} +
+ + {/* Content */} +

+ {event.title} +

+

+ {event.description} +

+ + {/* Date */} +
+ {new Date(event.eventDate).toLocaleDateString('zh-CN')} +
+ + {/* Source Link */} + {event.sourceUrl && ( + + 来源 → + + )} +
+ ); +} diff --git a/src/components/timeline/TimelineSection.tsx b/src/components/timeline/TimelineSection.tsx new file mode 100644 index 0000000..1319f20 --- /dev/null +++ b/src/components/timeline/TimelineSection.tsx @@ -0,0 +1,42 @@ +import { AIEvent } from '@prisma/client'; +import { EventCard } from './EventCard'; + +interface TimelineSectionProps { + year: number; + events: AIEvent[]; + index: number; +} + +export function TimelineSection({ year, events, index }: TimelineSectionProps) { + const isEven = index % 2 === 0; + + return ( +
+ {/* Year Label */} +
+
+ + {year} + +
+
+ + {/* Events Container */} +
+
+ {events.map((event, eventIndex) => ( + + ))} +
+
+
+ ); +}