feat(contact): add multilingual contact pages and dynamic configurations

- Created `src/pages/contact.astro` and `src/pages/zh/contact.astro` to support multilingual contact functionality.
- Introduced `contactIntents` and `contactMethods` dynamic configurations in `src/lib/data/contact.ts` for streamlined content management.
- Designed responsive and localized layouts with enhanced UX for both languages.
This commit is contained in:
zguiyang
2026-03-16 15:32:40 +08:00
parent 965ae613f8
commit ce6110588f
40 changed files with 3474 additions and 2547 deletions

View File

@@ -1,220 +1,70 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import HighlightBox from "@/components/markdown/HighlightBox.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import {
Sparkles,
Heart,
Zap,
Mail,
MessageSquare,
Send,
Github,
Twitter,
Linkedin,
Globe
} from "lucide-react";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import { personalInfo } from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const IconMap: Record<string, any> = {
mail: Mail,
wechat: MessageSquare,
qq: MessageSquare, // Lucide doesn't have QQ, using MessageSquare
send: Send,
github: Github,
twitter: Twitter,
linkedin: Linkedin,
globe: Globe,
};
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const lang = (Astro.currentLocale as Lang) || 'zh';
const t = useTranslations(lang);
const pageTitle = t("about.title");
const focusAreas = isZh
? ['前端架构设计与模块治理', '大型企业应用工程化交付', '金融与区块链系统前端建设', 'AI 协作开发流程实践']
: ['Frontend architecture and module governance', 'Large-scale enterprise application delivery', 'Financial and blockchain system engineering', 'AI-assisted development workflow practices'];
const experienceNotes = isZh
? [
'8 年专业开发经验,持续参与复杂业务系统建设。',
'参与多个政府与金融科技相关系统,包括交易平台、区块链基础设施、产业管理系统。',
'擅长跨角色协作与远程异步沟通,注重稳定交付与长期可维护性。',
]
: [
'8 years of professional development experience across complex business systems.',
'Contributed to government- and fintech-related systems, including trading platforms, blockchain infrastructure, and industrial management systems.',
'Strong in cross-functional collaboration and remote async execution with long-term maintainability in mind.',
];
---
<Layout title={pageTitle}>
<GlassHeader client:load transition:persist="header" lang={lang} />
<main class="min-h-screen relative overflow-hidden pt-16 sm:pt-20">
<!-- Background Decor -->
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
<div class="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 dark:from-primary/10 dark:via-transparent dark:to-primary/5"></div>
</div>
<Layout title={isZh ? '关于' : 'About'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<!-- Hero Section -->
<section class="relative z-10 py-16 md:py-24">
<Container>
<div class="max-w-4xl mx-auto">
<div class="flex flex-col md:flex-row items-center gap-12 mb-16">
<div class="relative group">
<div class="absolute -inset-1 rounded-full bg-gradient-to-r from-primary to-purple-600 opacity-75 blur transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div class="relative h-48 w-48 rounded-full border-4 border-background overflow-hidden bg-muted">
<img
src="/avatar.png"
alt="Joey Zhao"
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
onerror="this.src='https://ui-avatars.com/api/?name=Joy+Zhao&background=0D8ABC&color=fff&size=200'"
/>
</div>
</div>
<div class="text-center md:text-left space-y-6">
<h1 class="text-4xl md:text-6xl font-bold tracking-tight">
<span class="bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
{t('about.title')}
</span>
</h1>
<p class="text-xl text-muted-foreground leading-relaxed">
{t('about.description')}
</p>
</div>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '关于我' : 'About'}</h1>
<p class="mt-6 text-lg leading-relaxed text-muted-foreground">{personalInfo.description[lang]}</p>
</section>
<section class="page-content-main mt-12 grid gap-6 lg:grid-cols-2">
<article class="page-surface p-8">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '背景' : 'Background'}</h2>
<div class="mt-4 space-y-4 text-muted-foreground">
{personalInfo.about[lang].map((line) => <p>{line}</p>)}
</div>
</article>
<div class="grid gap-8">
<!-- Intro Box -->
<HighlightBox type="tip" title={t('about.intro.title')}>
<div class="space-y-4 py-2">
<p class="text-lg leading-relaxed whitespace-pre-line">{t('about.intro.content')}</p>
<div class="p-4 rounded-xl bg-primary/5 border border-primary/10 italic text-primary/80">
{t('about.intro.belief')}
</div>
</div>
</HighlightBox>
<article class="page-surface p-8">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '技术焦点' : 'Technical Focus'}</h2>
<ul class="mt-4 space-y-3 text-muted-foreground">
{focusAreas.map((item) => (
<li class="flex gap-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item}</span></li>
))}
</ul>
</article>
</section>
<!-- Self Intro -->
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Sparkles className="w-6 h-6 text-primary" />
{t('about.me.title')}
</h2>
<div class="prose dark:prose-invert max-w-none">
<p class="text-lg leading-relaxed text-muted-foreground whitespace-pre-line">
{t('about.me.content')}
</p>
</div>
</div>
<!-- Skills Grid -->
<div class="grid md:grid-cols-2 gap-8">
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Zap className="w-6 h-6 text-yellow-500" />
{t('about.skills.mastered.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.mastered.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-primary/5 border border-primary/10 text-muted-foreground text-xs font-medium">
{item}
</div>
))}
</div>
</div>
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Heart className="w-6 h-6 text-red-500" />
{t('about.skills.learning.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.learning.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-muted/30 border border-border/50 text-muted-foreground/70 text-xs font-medium">
{item}
</div>
))}
</div>
</div>
</div>
<!-- Interests -->
<div class="space-y-8">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Heart className="w-6 h-6 text-red-500" />
{t('about.interests.title')}
</h2>
<ul class="grid sm:grid-cols-2 gap-4">
{(t('about.interests.items') as unknown as any[]).map((item) => (
<li class="flex items-start gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-colors group">
<div class="mt-1.5 w-1.5 h-1.5 rounded-full bg-primary/40 group-hover:bg-primary transition-colors shrink-0" />
<div class="space-y-1">
<span class="font-bold text-foreground">{item.title}</span>
<p class="text-sm text-muted-foreground leading-relaxed">{item.content}</p>
</div>
</li>
))}
</ul>
</div>
<!-- Socials & Community -->
<div class="space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Globe className="w-6 h-6 text-primary" />
{t('about.socials.title')}
</h2>
<div class="flex flex-wrap gap-4 px-2">
{(t('about.socials.items') as unknown as any[]).map((item) => {
const Icon = IconMap[item.icon] || Globe;
return (
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-muted/30 border border-border/50 hover:border-primary/50 hover:bg-primary/5 transition-all group text-sm font-medium"
>
<Icon className="w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors" />
<span>{item.label}</span>
</a>
);
})}
</div>
</div>
<!-- Contact -->
<div class="space-y-8 py-8">
<div class="px-2 space-y-4">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Mail className="w-6 h-6 text-primary" />
{t('about.contact.title')}
</h2>
<p class="text-muted-foreground leading-relaxed max-w-2xl">
{t('about.contact.warning')}
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{(t('about.contact.methods') as unknown as any[]).map((method) => {
const Icon = IconMap[method.icon] || MessageSquare;
const content = (
<div class="flex items-center gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-all group">
<div class="p-2.5 rounded-lg bg-primary/5 text-primary/70 group-hover:bg-primary/10 group-hover:text-primary transition-colors">
<Icon className="w-5 h-5" />
</div>
<div class="flex flex-col">
<span class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">{method.label}</span>
<span class="text-sm font-medium break-all">{method.value}</span>
</div>
</div>
);
if (method.link) {
return (
<a href={method.link} class="block transition-transform hover:-translate-y-0.5">
{content}
</a>
);
}
return <div class="cursor-default">{content}</div>;
})}
</div>
</div>
</div>
</div>
</Container>
</section>
<section class="page-content-main mt-6 page-surface p-8">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '经验概览' : 'Experience'}</h2>
<ul class="mt-4 space-y-3 text-muted-foreground">
{experienceNotes.map((item) => (
<li class="flex gap-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item}</span></li>
))}
</ul>
</section>
</Container>
</main>
<Footer client:load lang={lang} />
<Footer lang={lang} client:load />
</Layout>