From 2474d51e1b25165724e524896e19e9854a684c58 Mon Sep 17 00:00:00 2001 From: zguiyang Date: Sat, 14 Mar 2026 11:41:45 +0800 Subject: [PATCH] feat(components): extract and reuse `ProjectCard` for cleaner project showcases - Created `ProjectCard` component to unify and streamline project card presentation. - Updated `index.astro`, `projects.astro`, and `zh/projects.astro` to use `ProjectCard` for improved maintainability and consistency. - Simplified layout and removed duplicate styles across pages. --- src/components/ProjectCard.astro | 123 +++++++++++++++++++++++++++++++ src/pages/index.astro | 48 +----------- src/pages/projects.astro | 61 +-------------- src/pages/zh/projects.astro | 61 +-------------- 4 files changed, 132 insertions(+), 161 deletions(-) create mode 100644 src/components/ProjectCard.astro diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro new file mode 100644 index 0000000..acb266d --- /dev/null +++ b/src/components/ProjectCard.astro @@ -0,0 +1,123 @@ +--- +import { useTranslations } from "@/i18n/utils"; +import type { Lang } from "@/types/i18n"; + +interface Props { + project: { + id: string; + title: string; + icon: string; + type: string; + status: string; + impact?: string; + description: string[]; + tech: string[]; + link: string; + featured?: boolean; + image?: { + bg: string; + hover?: string; + text?: string; + }; + links?: { + github?: string; + demo?: string; + }; + }; + lang: Lang; + showStatus?: boolean; +} + +const { project, lang, showStatus = false } = Astro.props; +const t = useTranslations(lang); + +const statusClassMap = { + building: "bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30", + completed: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border-emerald-500/30", + archived: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30", +} as const; + +const statusColor = statusClassMap[project.status as keyof typeof statusClassMap] ?? statusClassMap.completed; +--- + +
+ {/* 顶部封面图区域 */} +
+
+
+ {project.icon} +
+
+ + {t(`project.type.${project.type}`)} + + {project.featured && !showStatus && ( + + {t("project.featured")} + + )} +
+
+ + {/* 内容区域 */} +
+
+
+
+ {showStatus && ( +
+ + {t(`project.status.${project.status}`)} + +
+ )} +

{project.title}

+
+ {project.featured && showStatus && ( + + {t("project.featured")} + + )} +
+ +

+ {project.impact || project.description[0]} +

+ +
+ {project.tech.slice(0, 4).map((tech) => ( + + {tech} + + ))} +
+
+ + {/* 操作按钮 */} + +
+
diff --git a/src/pages/index.astro b/src/pages/index.astro index ed61091..510cc58 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -3,6 +3,7 @@ import Layout from "@/layouts/Layout.astro"; import GlassHeader from "@/components/GlassHeader"; import Footer from "@/components/Footer"; import Container from "@/components/ui/Container.astro"; +import ProjectCard from "@/components/ProjectCard.astro"; import { useTranslations } from "@/i18n/utils"; import type { Lang } from "@/types/i18n"; import { defaultLang } from "@/i18n/ui"; @@ -206,52 +207,9 @@ const keyFacts = [

{t("home.featured.title")}

-
+
{featuredProjects.map((project) => ( -
- {/* 顶部封面图区域 */} -
-
-
- {project.icon} -
-
- - {project.type} - -
-
- - {/* 内容区域 */} -
-
-

{project.title}

-

- {project.impact} -

- -
- {project.tech.map((tech) => ( - - {tech} - - ))} -
-
- - {/* 操作按钮 */} -
- - {t("home.featured.ctaPrimary")} - - {project.links?.github && ( - - - - )} -
-
-
+ ))}
diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 6d4fb9a..1c79a01 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -3,6 +3,7 @@ import Layout from "@/layouts/Layout.astro"; import GlassHeader from "@/components/GlassHeader"; import Footer from "@/components/Footer"; import Container from "@/components/ui/Container.astro"; +import ProjectCard from "@/components/ProjectCard.astro"; import { useTranslations } from "@/i18n/utils"; import type { Lang } from "@/types/i18n"; import { defaultLang } from "@/i18n/ui"; @@ -22,12 +23,6 @@ const filterOptions = [ { key: "client", label: t("project.type.client") }, { key: "experiment", label: t("project.type.experiment") }, ]; - -const statusClassMap = { - building: "bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30", - completed: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border-emerald-500/30", - archived: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30", -} as const; --- @@ -71,59 +66,9 @@ const statusClassMap = { ))}
-
+
{currentProjects.map((project) => ( -
-
-
-

- {project.icon} - {project.title} -

-

{t(`project.type.${project.type}`)}

-
-
- {project.featured && ( - - {t("project.featured")} - - )} - - {t(`project.status.${project.status}`)} - -
-
- -
- {project.description.slice(0, 2).map((desc) => ( -

{desc}

- ))} -
- -
- {project.tech.slice(0, 4).map((tech) => ( - {tech} - ))} -
- -
- - {t('project.visit')} - - - - - {project.links?.github && ( - - GitHub - - )} -
-
+ ))}
diff --git a/src/pages/zh/projects.astro b/src/pages/zh/projects.astro index 6d4fb9a..1c79a01 100644 --- a/src/pages/zh/projects.astro +++ b/src/pages/zh/projects.astro @@ -3,6 +3,7 @@ import Layout from "@/layouts/Layout.astro"; import GlassHeader from "@/components/GlassHeader"; import Footer from "@/components/Footer"; import Container from "@/components/ui/Container.astro"; +import ProjectCard from "@/components/ProjectCard.astro"; import { useTranslations } from "@/i18n/utils"; import type { Lang } from "@/types/i18n"; import { defaultLang } from "@/i18n/ui"; @@ -22,12 +23,6 @@ const filterOptions = [ { key: "client", label: t("project.type.client") }, { key: "experiment", label: t("project.type.experiment") }, ]; - -const statusClassMap = { - building: "bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30", - completed: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border-emerald-500/30", - archived: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30", -} as const; --- @@ -71,59 +66,9 @@ const statusClassMap = { ))} -
+
{currentProjects.map((project) => ( -
-
-
-

- {project.icon} - {project.title} -

-

{t(`project.type.${project.type}`)}

-
-
- {project.featured && ( - - {t("project.featured")} - - )} - - {t(`project.status.${project.status}`)} - -
-
- -
- {project.description.slice(0, 2).map((desc) => ( -

{desc}

- ))} -
- -
- {project.tech.slice(0, 4).map((tech) => ( - {tech} - ))} -
- -
- - {t('project.visit')} - - - - - {project.links?.github && ( - - GitHub - - )} -
-
+ ))}