时间线页面
前言
Firefly 主题移植了一个专业的时间线(Timeline)页面,用于按时间顺序展示您的人生重要事件、工作经历、教育背景和项目成就等。这个页面可以帮助访客了解您的成长历程和专业发展。
本文档详细介绍如何将技能页面功能移植到 Firefly-Hyde 主题中。
在开始之前,请确保你已经按照文件结构创建好对应的目录和文件。
📁 文件结构
src/
├── components/
│ └── features/
│ └── timeline/
│ ├── index.ts ✨ 新增 - 组件导出
│ ├── types.ts ✨ 新增 - 类型定义
│ └── TimelineCard.astro ✨ 新增 - 时间线卡片组件
├── config/
│ └── siteConfig.ts ✏️ 修改 - 添加页面开关配置
│ └── navBarConfig.ts ✏️ 修改 - 导航栏配置
├── constants/
│ └── link-presets.ts ✏️ 修改 - 添加导航链接
├── data/
│ └── timeline.ts ✨ 新增 - 时间线数据管理
├── i18n/
│ ├── i18nKey.ts ✏️ 修改 - 添加翻译键
│ └── languages/
│ ├── en.ts ✏️ 修改 - 英文翻译
│ ├── zh_CN.ts ✏️ 修改 - 中文翻译
│ ├── zh_TW.ts ✏️ 修改 - 繁体翻译
│ ├── ja.ts ✏️ 修改 - 日文翻译
│ └── ru.ts ✏️ 修改 - 俄文翻译
├── pages/
│ └── timeline.astro ✨ 新增 - 时间线页面
├── types/
│ └── config.ts ✏️ 修改 - 添加类型定义创建时间线卡片组件
文件路径:
src/components/features/timeline/index.ts组件导出文件路径:
src/components/features/timeline/types.ts类型定义文件路径:
src/components/features/timeline/TimelineCard.astro时间线卡片组件
export { default as SkillCard } from "./SkillCard.astro";
export * from "./types";import type { Skill } from "../../../data/skills";
export interface SkillCardProps {
skill: Skill;
}---
import I18nKey from "../../../i18n/i18nKey";
import { i18n } from "../../../i18n/translation";
import Icon from "../../misc/Icon.astro";
import type { SkillCardProps } from "./types";
const { skill } = Astro.props as SkillCardProps;
const skillColor = skill.color || "#3B82F6";
const getLevelText = (level: string) => {
switch (level) {
case "expert":
return i18n(I18nKey.skillsExpert);
case "advanced":
return i18n(I18nKey.skillsAdvanced);
case "intermediate":
return i18n(I18nKey.skillsIntermediate);
case "beginner":
return i18n(I18nKey.skillsBeginner);
default:
return level;
}
};
const getLevelWidth = (level: string) => {
switch (level) {
case "expert":
return "100%";
case "advanced":
return "80%";
case "intermediate":
return "60%";
case "beginner":
return "40%";
default:
return "20%";
}
};
const formatExperience = (exp: { years: number; months: number }) => {
const parts: string[] = [];
if (exp.years > 0) {
parts.push(`${exp.years} ${i18n(I18nKey.skillYears)}`);
}
if (exp.months > 0) {
parts.push(`${exp.months} ${i18n(I18nKey.skillMonths)}`);
}
return parts.join(" ");
};
---
<div
class="skill-card group relative bg-transparent rounded-xl border border-black/10 dark:border-white/10 overflow-hidden transition-all duration-300 hover:shadow-xl hover:-translate-y-1"
data-category={skill.category}
>
<div class="p-5">
<div class="flex items-start gap-4 mb-3">
<div
class="w-12 h-12 flex-shrink-0 rounded-lg flex items-center justify-center"
style={`background-color: ${skillColor}20`}
>
<Icon
icon={skill.icon}
class="text-xl"
color={skillColor}
fallback={skill.name.charAt(0)}
loading="eager"
/>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3
class="text-lg font-bold text-black/90 dark:text-white/90 truncate group-hover:text-[var(--primary)] transition-colors duration-200"
>
{skill.name}
</h3>
<span
class="shrink-0 ml-2 px-2 py-0.5 text-xs rounded-md bg-[var(--primary)]/10 text-[var(--primary)] font-medium"
>
{getLevelText(skill.level)}
</span>
</div>
<p class="text-xs text-black/50 dark:text-white/50">
{formatExperience(skill.experience)}
</p>
</div>
</div>
<p
class="text-sm text-black/60 dark:text-white/60 line-clamp-2 min-h-[2.5rem]"
>
{skill.description}
</p>
<div class="mt-3 w-full bg-[var(--btn-regular-bg)] rounded-full h-1.5">
<div
class="h-1.5 rounded-full transition-all duration-500"
style={`width: ${getLevelWidth(skill.level)}; background-color: ${skillColor}`}
>
</div>
</div>
</div>
<div
class="absolute inset-0 bg-gradient-to-br from-[var(--primary)]/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"
>
</div>
</div>
<style>
.skill-card {
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}
.skill-card.filtered-out {
display: none;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.skill-card:nth-child(1) {
animation-delay: 0.03s;
}
.skill-card:nth-child(2) {
animation-delay: 0.06s;
}
.skill-card:nth-child(3) {
animation-delay: 0.09s;
}
.skill-card:nth-child(4) {
animation-delay: 0.12s;
}
.skill-card:nth-child(5) {
animation-delay: 0.15s;
}
.skill-card:nth-child(6) {
animation-delay: 0.18s;
}
.skill-card:nth-child(7) {
animation-delay: 0.21s;
}
.skill-card:nth-child(8) {
animation-delay: 0.24s;
}
.skill-card:nth-child(9) {
animation-delay: 0.27s;
}
.skill-card:nth-child(10) {
animation-delay: 0.3s;
}
.skill-card:nth-child(11) {
animation-delay: 0.33s;
}
.skill-card:nth-child(12) {
animation-delay: 0.36s;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>页面开关导航栏配置
在
SiteConfig类型中添加页面开关配置,文件路径:src/config/siteConfig.ts在
navBarConfig类型中更新导航栏配置,文件路径:src/config/navBarConfig.ts
// 页面开关配置
pages: {
// ... 其他配置
// 时间线页面开关
timeline: true,
},// 我的及其子菜单
links.push({
name: "我的",
url: "/my/",
icon: "material-symbols:person",
children: [
// 根据配置决定是否添加时间线,在siteConfig关闭pages.timeline时导航栏不显示时间线
...(siteConfig.pages.timeline ? [LinkPreset.Timeline] : []),
],
});更新类型配置链接
在
types类型的config中添加类型定义,文件路径:src/types/config.ts在
constants类型的link-presets添加到导航链接,文件路径:src/constants/link-presets.ts
export type SiteConfig = {
// 页面开关配置
pages: {
timeline?: boolean; // 时间线页面开关
};
}
export enum LinkPreset {
Timeline = 11, // ✨ 新增
}export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
[LinkPreset.Timeline]: {
name: i18n(I18nKey.timeline),
url: "/timeline/",
icon: "material-symbols:timeline",
},
}时间线数据管理
文件路径:src/data/timeline.ts
import type { TimelineItem } from "../components/features/timeline/types";
export const timelineData: TimelineItem[] = [
{
id: "current-study",
title: "Studying Computer Science and Technology",
description:
"Currently studying Computer Science and Technology, focusing on web development and software engineering.",
type: "education",
startDate: "2022-09-01",
location: "Beijing",
organization: "Beijing Institute of Technology",
skills: ["Java", "Python", "JavaScript", "HTML/CSS", "MySQL"],
achievements: [
"Current GPA: 3.6/4.0",
"Completed data structures and algorithms course project",
"Participated in multiple course project developments",
],
icon: "material-symbols:school",
color: "#059669",
featured: true,
},
{
id: "mizuki-blog-project",
title: "Mizuki Personal Blog Project",
description:
"A personal blog website developed using the Astro framework as a practical project for learning frontend technologies.",
type: "project",
startDate: "2024-06-01",
endDate: "2024-08-01",
skills: ["Astro", "TypeScript", "Tailwind CSS", "Git"],
achievements: [
"Mastered modern frontend development tech stack",
"Learned responsive design and user experience optimization",
"Completed the full process from design to deployment",
],
links: [
{
name: "GitHub Repository",
url: "https://github.com/example/mizuki-blog",
type: "project",
},
{
name: "Live Demo",
url: "https://mizuki-demo.example.com",
type: "website",
},
],
icon: "material-symbols:code",
color: "#7C3AED",
featured: true,
},
{
id: "summer-internship-2024",
title: "Frontend Development Intern",
description:
"Summer internship at an internet company, participating in frontend development of web applications.",
type: "work",
startDate: "2024-07-01",
endDate: "2024-08-31",
location: "Beijing",
organization: "TechStart Internet Company",
position: "Frontend Development Intern",
skills: ["React", "JavaScript", "CSS3", "Git", "Figma"],
achievements: [
"Completed user interface component development",
"Learned team collaboration and code standards",
"Received outstanding internship performance certificate",
],
icon: "material-symbols:work",
color: "#DC2626",
featured: true,
},
{
id: "web-development-course",
title: "Completed Web Development Online Course",
description:
"Completed a full-stack web development online course, systematically learning frontend and backend development technologies.",
type: "achievement",
startDate: "2024-01-15",
endDate: "2024-05-30",
organization: "Mooc Website",
skills: ["HTML", "CSS", "JavaScript", "Node.js", "Express"],
achievements: [
"Received course completion certificate",
"Completed 5 practical projects",
"Mastered full-stack development fundamentals",
],
links: [
{
name: "Course Certificate",
url: "https://certificates.example.com/web-dev",
type: "certificate",
},
],
icon: "material-symbols:verified",
color: "#059669",
},
{
id: "student-management-system",
title: "Student Management System Course Project",
description:
"Final project for the database course, developed a complete student information management system.",
type: "project",
startDate: "2023-11-01",
endDate: "2023-12-15",
skills: ["Java", "MySQL", "Swing", "JDBC"],
achievements: [
"Received excellent course project grade",
"Implemented complete CRUD functionality",
"Learned database design and optimization",
],
icon: "material-symbols:database",
color: "#EA580C",
},
{
id: "programming-contest",
title: "University Programming Contest",
description:
"Participated in a programming contest held by the university, improving algorithm and programming skills.",
type: "achievement",
startDate: "2023-10-20",
location: "Beijing Institute of Technology",
organization: "School of Computer Science",
skills: ["C++", "Algorithms", "Data Structures"],
achievements: [
"Won third prize in university contest",
"Improved algorithmic thinking ability",
"Strengthened programming fundamentals",
],
icon: "material-symbols:emoji-events",
color: "#7C3AED",
},
{
id: "part-time-tutor",
title: "Part-time Programming Tutor",
description:
"Provided programming tutoring for high school students, helping them learn Python basics.",
type: "work",
startDate: "2023-09-01",
endDate: "2024-01-31",
position: "Programming Tutor",
skills: ["Python", "Teaching", "Communication"],
achievements: [
"Helped 3 students master Python basics",
"Improved expression and communication skills",
"Gained teaching experience",
],
icon: "material-symbols:school",
color: "#059669",
},
{
id: "high-school-graduation",
title: "High School Graduation",
description:
"Graduated from high school with excellent grades and was admitted to the Computer Science and Technology program at Beijing Institute of Technology.",
type: "education",
startDate: "2019-09-01",
endDate: "2022-06-30",
location: "Jinan, Shandong",
organization: "No.1 High School of Jinan",
achievements: [
"College entrance exam score: 620",
"Received municipal model student award",
"Won provincial second prize in math competition",
],
icon: "material-symbols:school",
color: "#2563EB",
},
{
id: "first-programming-experience",
title: "First Programming Experience",
description:
"First encountered programming in high school IT class, started learning Python basic syntax.",
type: "education",
startDate: "2021-03-01",
skills: ["Python", "Basic Programming Concepts"],
achievements: [
'Completed first "Hello World" program',
"Learned basic loops and conditional statements",
"Developed interest in programming",
],
icon: "material-symbols:code",
color: "#7C3AED",
},
];添加国际化翻译
添加翻译键,文件路径:
src/i18n/i18nKey.ts添加英文翻译,文件路径:
src/i18n/languages/en.ts添加中文翻译,文件路径:
src/i18n/languages/zh_CN.ts添加繁体中文翻译,文件路径:
src/i18n/languages/zh_TW.ts添加日文翻译,文件路径:
src/i18n/languages/ja.ts添加俄文翻译,文件路径:
src/i18n/languages/ru.ts
export enum I18nKey {
// 时间线页面
timeline = "timeline",
timelineSubtitle = "timelineSubtitle",
timelineEducation = "timelineEducation",
timelineWork = "timelineWork",
timelineProject = "timelineProject",
timelineAchievement = "timelineAchievement",
timelinePresent = "timelinePresent",
timelineLocation = "timelineLocation",
timelineDescription = "timelineDescription",
timelineMonths = "timelineMonths",
timelineYears = "timelineYears",
timelineTotal = "timelineTotal",
timelineProjects = "timelineProjects",
timelineExperience = "timelineExperience",
timelineCurrent = "timelineCurrent",
timelineHistory = "timelineHistory",
timelineAchievements = "timelineAchievements",
timelineStartDate = "timelineStartDate",
timelineDuration = "timelineDuration",
} // Timeline Page
[Key.timeline]: "Timeline",
[Key.timelineSubtitle]: "My growth journey and important milestones",
[Key.timelineEducation]: "Education",
[Key.timelineWork]: "Work Experience",
[Key.timelineProject]: "Project Experience",
[Key.timelineAchievement]: "Achievements",
[Key.timelinePresent]: "Present",
[Key.timelineLocation]: "Location",
[Key.timelineDescription]: "Detailed Description",
[Key.timelineMonths]: "months",
[Key.timelineYears]: "years",
[Key.timelineTotal]: "Total",
[Key.timelineProjects]: "Projects",
[Key.timelineExperience]: "Work Experience",
[Key.timelineCurrent]: "Current Status",
[Key.timelineHistory]: "History",
[Key.timelineAchievements]: "Achievements",
[Key.timelineStartDate]: "Start Date",
[Key.timelineDuration]: "Duration", // 时间线页面
[Key.timeline]: "时间线",
[Key.timelineSubtitle]: "我的成长历程和重要里程碑",
[Key.timelineEducation]: "教育经历",
[Key.timelineWork]: "工作经历",
[Key.timelineProject]: "项目经历",
[Key.timelineAchievement]: "成就荣誉",
[Key.timelinePresent]: "至今",
[Key.timelineLocation]: "地点",
[Key.timelineDescription]: "详细描述",
[Key.timelineMonths]: "个月",
[Key.timelineYears]: "年",
[Key.timelineTotal]: "总计",
[Key.timelineProjects]: "项目数",
[Key.timelineExperience]: "工作经验",
[Key.timelineCurrent]: "当前状态",
[Key.timelineHistory]: "历史记录",
[Key.timelineAchievements]: "成就荣誉",
[Key.timelineStartDate]: "开始日期",
[Key.timelineDuration]: "持续时间", // 時間線頁面
[Key.timeline]: "時間線",
[Key.timelineSubtitle]: "我的成長歷程和重要里程碑",
[Key.timelineEducation]: "教育經歷",
[Key.timelineWork]: "工作經歷",
[Key.timelineProject]: "專案經歷",
[Key.timelineAchievement]: "成就榮譽",
[Key.timelinePresent]: "至今",
[Key.timelineLocation]: "地點",
[Key.timelineDescription]: "詳細描述",
[Key.timelineMonths]: "個月",
[Key.timelineYears]: "年",
[Key.timelineTotal]: "總計",
[Key.timelineProjects]: "專案數",
[Key.timelineExperience]: "工作經驗",
[Key.timelineCurrent]: "當前狀態",
[Key.timelineHistory]: "歷史記錄",
[Key.timelineAchievements]: "成就榮譽",
[Key.timelineStartDate]: "開始日期",
[Key.timelineDuration]: "持續時間", // タイムラインページ
[Key.timeline]: "タイムライン",
[Key.timelineSubtitle]: "成長への旅と重要なマイルストーン",
[Key.timelineEducation]: "教育",
[Key.timelineWork]: "実務経験",
[Key.timelineProject]: "プロジェクト経験",
[Key.timelineAchievement]: "実績",
[Key.timelinePresent]: "現在",
[Key.timelineLocation]: "場所",
[Key.timelineDescription]: "詳細な説明",
[Key.timelineMonths]: "ヶ月",
[Key.timelineYears]: "年",
[Key.timelineTotal]: "合計",
[Key.timelineProjects]: "プロジェクト",
[Key.timelineExperience]: "実務経験",
[Key.timelineCurrent]: "現在のステータス",
[Key.timelineHistory]: "履歴",
[Key.timelineAchievements]: "実績",
[Key.timelineStartDate]: "開始日",
[Key.timelineDuration]: "期間", // Страница временной шкалы
[Key.timeline]: "Временная шкала",
[Key.timelineSubtitle]: "Мой путь роста и важные вехи",
[Key.timelineEducation]: "Образование",
[Key.timelineWork]: "Опыт работы",
[Key.timelineProject]: "Проекты",
[Key.timelineAchievement]: "Достижения",
[Key.timelinePresent]: "Настоящее время",
[Key.timelineLocation]: "Местоположение",
[Key.timelineDescription]: "Подробное описание",
[Key.timelineMonths]: "месяцев",
[Key.timelineYears]: "лет",
[Key.timelineTotal]: "Всего",
[Key.timelineProjects]: "Количество проектов",
[Key.timelineExperience]: "Опыт работы",
[Key.timelineCurrent]: "Текущий статус",
[Key.timelineHistory]: "История",
[Key.timelineAchievements]: "Достижения",
[Key.timelineStartDate]: "Дата начала",
[Key.timelineDuration]: "Продолжительность",创建时间线页面
文件路径:src/pages/timeline.astro
---
import { FilterTabs } from "@components/atoms/";
import { TimelineCard } from "@components/features/timeline";
import { PageHeader } from "@components/features/page-header";
import MainGridLayout from "@layouts/MainGridLayout.astro";
import { Icon } from "astro-icon/components";
import { siteConfig } from "../config";
import { timelineData } from "../data/timeline";
import I18nKey from "../i18n/i18nKey";
import { i18n } from "../i18n/translation";
if (!siteConfig.pages.timeline) {
return Astro.redirect("/404/");
}
const types = ["education", "work", "project", "achievement"] as const;
const getTypeIcon = (type: string) => {
switch (type) {
case "education":
return "material-symbols:school";
case "work":
return "material-symbols:work";
case "project":
return "material-symbols:code";
case "achievement":
return "material-symbols:emoji-events";
default:
return "material-symbols:apps";
}
};
const getTypeText = (type: string) => {
switch (type) {
case "education":
return i18n(I18nKey.timelineEducation);
case "work":
return i18n(I18nKey.timelineWork);
case "project":
return i18n(I18nKey.timelineProject);
case "achievement":
return i18n(I18nKey.timelineAchievement);
default:
return type;
}
};
const filterTabs = [
{
value: "all",
label: i18n(I18nKey.friendsFilterAll),
icon: "material-symbols:apps",
count: timelineData.length,
},
...types.map((type) => ({
value: type,
label: getTypeText(type),
icon: getTypeIcon(type),
count: timelineData.filter((item) => item.type === type).length,
})),
];
const title = i18n(I18nKey.timeline);
const subtitle = i18n(I18nKey.timelineSubtitle);
---
<MainGridLayout title={title} description={subtitle}>
<script>
// import("../scripts/right-sidebar-layout.js");
import { loadIconify } from "../utils/icon-loader";
loadIconify().catch((error) => {
console.error("Failed to load Iconify:", error);
});
</script>
<script is:inline src="/js/filter-tabs-handler.js"></script>
<div
class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32"
>
<div class="card-base z-10 px-6 sm:px-9 py-6 relative w-full">
<PageHeader title={title} subtitle={subtitle} />
<div class="mb-8">
<FilterTabs tabs={filterTabs} dataAttr="type" />
</div>
<div id="timeline-list" class="timeline-list">
{timelineData.map((item) => <TimelineCard item={item} />)}
</div>
<div id="no-results" class="hidden text-center py-16">
<Icon
name="material-symbols:search-off-rounded"
class="text-6xl text-black/15 dark:text-white/15 mb-4"
/>
<p class="text-black/40 dark:text-white/40 text-lg">
No matching items
</p>
</div>
</div>
</div>
</MainGridLayout>
<style>
.timeline-list {
position: relative;
}
.timeline-list::before {
content: "";
position: absolute;
left: 9px;
top: 0;
bottom: 0;
width: 2px;
background: var(--line-divider);
border-radius: 1px;
}
</style>