From 317aec91ab3398fea6a74d224e7dff17c589bfa6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 2 Oct 2026 23:12:50 +0800 Subject: [PATCH] feat(about): enhance About page with new banner and sidebar components, improve layout and animations, and add styling for tech stack and contact information --- frontend/src/app/(blog)/about/page.tsx | 76 +++-- frontend/src/app/globals.css | 151 +++++++++ frontend/src/components/about/AboutMain.tsx | 192 +++++------- .../src/components/about/AboutSidebar.tsx | 222 ++++++++------ .../src/components/about/BannerUploader.tsx | 62 +++- mockup/about.html | 287 ++++++++++++++++++ 6 files changed, 733 insertions(+), 257 deletions(-) create mode 100644 mockup/about.html diff --git a/frontend/src/app/(blog)/about/page.tsx b/frontend/src/app/(blog)/about/page.tsx index fdfaf85..018047c 100644 --- a/frontend/src/app/(blog)/about/page.tsx +++ b/frontend/src/app/(blog)/about/page.tsx @@ -5,14 +5,25 @@ import { getAbout, type AboutInfo } from "@/lib/about"; import { useIsAdmin } from "@/hooks/useIsAdmin"; import { AboutSidebar } from "@/components/about/AboutSidebar"; import { AboutMain } from "@/components/about/AboutMain"; +import { BannerUploader } from "@/components/about/BannerUploader"; import { Skeleton } from "@/components/ui/skeleton"; /** - * 关于页:左侧个人卡片 + 右侧主内容。 + * 关于页: + * ┌────────────────────────────────────────────┐ + * │ Hero Banner(mesh + 粒子) │ + * ├──────────────┬─────────────────────────────┤ + * │ Sidebar │ Main(About Me / Skills / │ + * │ - Identity │ Bio) │ + * │ - 联系方式 │ │ + * │ - Tech Stack│ │ + * └──────────────┴─────────────────────────────┘ * - * - 公共部分(头像、昵称、bio、tech stack 等)所有人都能看到; - * - 「编辑资料」按钮和头像/Banner 的 hover 上传交互仅对管理员暴露, - * 借 useIsAdmin hook 判断(token 存在即可编辑)。 + * 两栏 grid 用 -mt-16 上移,让身份卡里的 -mt-12 头像正好叠到 banner 下沿, + * 跟 mockup 的「头像压住两块」视觉一致。 + * + * - 公共部分(昵称、bio、tech stack 等)所有人都能看到; + * - 头像 / Banner hover 上传、「编辑资料」按钮仅对管理员暴露。 * * 注意:头像/Banner 上传后只更新本地预览,不会自动落库——落库需要 * 管理员在「站点设置」页点击「保存」按钮(avatar/banner 已合并进表单)。 @@ -48,17 +59,21 @@ export default function AboutPage() { if (loading) { return ( -
-
- - - - -
-
- - - +
+ {/* Banner 骨架 */} + + {/* 两栏骨架 */} +
+
+ + + +
+
+ + + +
); @@ -66,24 +81,31 @@ export default function AboutPage() { if (!data) { return ( -
暂无关于信息
+
+ 暂无关于信息 +
); } return ( -
- + {/* Hero Banner */} + - + + {/* 两栏:-mt-16 让身份卡里的头像(-mt-12)正好叠到 banner 下沿 */} +
+ + +
); } \ No newline at end of file diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index aa4e0e7..9d9f02d 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -842,6 +842,157 @@ border-color: rgba(239, 68, 68, 0.3); } +/* ===== 关于页:Mesh Banner / 粒子 / 侧栏小标题 / Tech 图标 ===== */ +.banner-mesh { + background-color: #1e3a8a; + background-image: + radial-gradient(at 18% 24%, rgba(59, 130, 246, 0.85) 0px, transparent 55%), + radial-gradient(at 82% 18%, rgba(99, 102, 241, 0.7) 0px, transparent 55%), + radial-gradient(at 72% 78%, rgba(96, 165, 250, 0.6) 0px, transparent 55%), + linear-gradient(135deg, #1e3a8a 0%, #2563eb 50%, #1e40af 100%); +} +.banner-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px); + background-size: 60px 60px; + -webkit-mask-image: radial-gradient(ellipse at center, black 25%, transparent 75%); + mask-image: radial-gradient(ellipse at center, black 25%, transparent 75%); + pointer-events: none; +} +.banner-grid::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent); +} + +@keyframes drift { + 0%, + 100% { + transform: translateY(0); + opacity: 0.5; + } + 50% { + transform: translateY(-10px); + opacity: 1; + } +} +.particle { + animation: drift 6s ease-in-out infinite; +} + +@keyframes rise { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: none; + } +} +.reveal-up { + animation: rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* 侧栏小标题:左竖条 + 文字 */ +.side-title { + font-size: 0.875rem; + font-weight: 600; + color: var(--foreground); + display: inline-flex; + align-items: center; + gap: 0.5rem; +} +.side-title::before { + content: ""; + width: 0.25rem; + height: 1rem; + border-radius: 9999px; + background: var(--primary); + flex-shrink: 0; +} + +.link-brand { + color: var(--muted-foreground); + transition: color 150ms ease; +} +.link-brand:hover { + color: var(--primary); +} + +/* Tech Stack 32×32 小图标 */ +.tech-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + color: #fff; + font-weight: 700; + font-size: 11px; + font-family: var(--font-mono, ui-monospace, monospace); + box-shadow: 0 4px 10px -3px rgba(0, 0, 0, 0.18); + transition: transform 0.15s ease; + cursor: default; +} +.tech-icon:hover { + transform: translateY(-2px); +} + +/* 头像环:内白 + 外品牌蓝 */ +.avatar-ring { + box-shadow: + 0 0 0 4px var(--card), + 0 0 0 6px var(--primary), + 0 8px 24px -6px rgba(0, 0, 0, 0.18); +} +/* 头像占位:品牌渐变 + 首字母 */ +.avatar-placeholder { + background: linear-gradient(135deg, #60a5fa 0%, #2563eb 50%, #1e3a8a 100%); + color: #fff; + font-weight: 700; + letter-spacing: -0.02em; + text-transform: lowercase; +} + +/* 身份卡:上方贴出大头像的「口袋」容器 */ +/* Card 默认 overflow-hidden + hover 上移; + 身份卡既要可见(让 -mt-12 的头像不被裁),又不能 hover 上移(避免头像视觉抖动)。 */ +.card-static { + overflow: visible !important; +} +.card-static:hover { + --tw-translate-y: 0 !important; + translate: none !important; +} + +/* Banner 标签条(pill 样式) */ +.banner-pill { + display: inline-flex; + align-items: center; + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + font-size: 11px; + line-height: 1.4; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +@media (prefers-reduced-motion: reduce) { + .particle, + .reveal-up { + animation: none; + } +} + /* ===== DataTable 状态徽章(spec §5.13) ===== */ .status-badge { display: inline-flex; diff --git a/frontend/src/components/about/AboutMain.tsx b/frontend/src/components/about/AboutMain.tsx index fcf9405..5d8d6bc 100644 --- a/frontend/src/components/about/AboutMain.tsx +++ b/frontend/src/components/about/AboutMain.tsx @@ -1,140 +1,100 @@ "use client"; import type { AboutInfo } from "@/lib/about"; -import { BannerUploader } from "./BannerUploader"; +import { Card, CardContent } from "@/components/ui/card"; interface AboutMainProps { data: AboutInfo; - editable: boolean; - bannerValue: string; - onBannerChange?: (url: string) => void; } /** - * 关于页右侧主体: - * - 顶部 banner(可由管理员更换) - * - 主标题 "Hi, I'm [昵称]" + 副标题 - * - "About Me" 项目列表(emoji + 文本) - * - "Tech Stack" 技术栈图标网格 - * - 后端 / 前端 / 其他技能(若有) + * 关于页右侧主体内容: + * - About Me 项目列表 + * - Skills(Backend / Frontend / Other) + * - Bio 长文 + * + * 注:banner 由 page.tsx 单独管理(要让它在 grid 上方,跟身份卡重叠)。 */ -export function AboutMain({ - data, - editable, - bannerValue, - onBannerChange, -}: AboutMainProps) { - const hasBanner = Boolean(bannerValue); - +export function AboutMain({ data }: AboutMainProps) { return ( -
- {hasBanner && ( - onBannerChange?.(url)} - editable={editable} - /> - )} - - {/* Greeting 区块 */} -
-

- 🎉 Hi, I'm {data.nickname || "未命名"} -

- {data.subtitle && ( -

- {data.subtitle} -

- )} -
- +
{data.aboutPoints.length > 0 && ( -
-

- 🌱 - About Me -

-
    - {data.aboutPoints.map((p, idx) => ( -
  • - {p.emoji || "•"} - {p.text} -
  • - ))} -
-
- )} - - {data.techStack.length > 0 && ( -
-

- 🛠 - Tech Stack -

-
- {data.techStack.map((t, idx) => ( - - {t.icon || t.name?.[0]?.toUpperCase() || "?"} - - ))} -
-
+ + +

+ 🌱 + About Me +

+
    + {data.aboutPoints.map((p, idx) => ( +
  • + {p.emoji || "•"} + {p.text} +
  • + ))} +
+
+
)} {(data.backendSkills || data.frontendSkills || data.otherSkills) && ( -
-

- 💻 - Skills -

-
- {data.backendSkills && ( -
-
- Backend -
-
- {data.backendSkills} -
-
- )} - {data.frontendSkills && ( -
-
- Frontend + + +

+ 💻 + Skills +

+
+ {data.backendSkills && ( +
+
+ Backend +
+
+ {data.backendSkills} +
-
- {data.frontendSkills} + )} + {data.frontendSkills && ( +
+
+ Frontend +
+
+ {data.frontendSkills} +
-
- )} - {data.otherSkills && ( -
-
- Other + )} + {data.otherSkills && ( +
+
+ Other +
+
+ {data.otherSkills} +
-
{data.otherSkills}
-
- )} -
-
+ )} +
+ + )} {data.bio && ( -
-

- 📝 - Bio -

-

- {data.bio} -

-
+ + +

+ 📝 + Bio +

+

+ {data.bio} +

+
+
)}
); diff --git a/frontend/src/components/about/AboutSidebar.tsx b/frontend/src/components/about/AboutSidebar.tsx index 03e67f5..329df48 100644 --- a/frontend/src/components/about/AboutSidebar.tsx +++ b/frontend/src/components/about/AboutSidebar.tsx @@ -1,9 +1,10 @@ "use client"; import Link from "next/link"; -import { Pencil, Link2, Mail } from "lucide-react"; +import { Globe, Pencil, Mail } from "lucide-react"; import type { AboutInfo } from "@/lib/about"; import { AvatarUploader } from "./AvatarUploader"; +import { Card, CardContent } from "@/components/ui/card"; import { cn } from "@/lib/utils"; interface AboutSidebarProps { @@ -11,19 +12,16 @@ interface AboutSidebarProps { editable: boolean; /** 头像上传后回调(仅在 editable 时有效) */ onAvatarChange?: (url: string) => void; - /** 头像 URL 受控值(编辑场景下使用,传入 AvatarUploader) */ + /** 头像 URL 受控值 */ avatarValue: string; className?: string; } /** * 关于页左侧栏: - * [大头像] 昵称 · pronouns - * 一句话 intro - * [编辑资料] 按钮(管理员) - * followers · following - * 邮箱 + 网站链接 - * Achievements 徽章 + * ① 身份卡:头像 + 昵称 + pronouns + 一句话 intro + 编辑资料(重叠在 banner 下沿) + * ② 联系方式:邮箱 / 网站 + * ③ Tech Stack:5×n 技术图标网格 */ export function AboutSidebar({ data, @@ -32,104 +30,132 @@ export function AboutSidebar({ avatarValue, className, }: AboutSidebarProps) { - return ( - + + +
+ +
+

+ 🌱 About Me +

+
    +
  • 🤖Interested in AI Agent & Full-stack development, currently diving deep into both fields.
  • +
  • 🌿Love open source, keep learning and building practical projects.
  • +
  • ⚙️Exploring systems programming & big data ecosystem.
  • +
  • 🌙Enjoy anime in spare time.
  • +
  • 📮Contact: ideast2025@gmail.com
  • +
+
+ +
+

+ 💻 Skills +

+
+
+
Backend
+
Go / Python / FastAPI +PostgreSQL / Redis +Docker +分布式 / 大数据生态
+
+
+
Frontend
+
TypeScript / JavaScript +React / Next.js +Node.js +Tailwind / Radix
+
+
+
Other
+
Rust (学习中) +Git / GitHub +AI Agent / LLM +二次元
+
+
+
+ +
+

+ 📝 Bio +

+

+一个对 AI Agent 和系统编程充满好奇的全栈开发者。 +喜欢用代码把模糊的想法变成能用的东西, +也愿意把踩过的坑写成笔记分享给同样在路上的人。 + +持续学习,持续输出,欢迎一起交流。🌙

+
+
+ + + + + +