|
| 1 | +import type { CSSProperties, ReactNode } from 'react'; |
| 2 | +import Link from 'next/link'; |
| 3 | +import { |
| 4 | + PiArrowDownBold, |
| 5 | + PiArrowLeftBold, |
| 6 | + PiArrowRightBold, |
| 7 | + PiArrowUpRightBold, |
| 8 | + PiCheckCircle, |
| 9 | + PiGithubLogo, |
| 10 | + PiLightbulb, |
| 11 | + PiMagnifyingGlass, |
| 12 | + PiWrench, |
| 13 | +} from 'react-icons/pi'; |
| 14 | + |
| 15 | +export type DetailStep = { |
| 16 | + label: string; |
| 17 | + title: string; |
| 18 | + copy: string; |
| 19 | + icon: 'problem' | 'reasoning' | 'solution' | 'result'; |
| 20 | +}; |
| 21 | + |
| 22 | +type DetailPageProps = { |
| 23 | + category: string; |
| 24 | + title: string; |
| 25 | + subtitle: string; |
| 26 | + intro: string; |
| 27 | + accent: string; |
| 28 | + highlight: string; |
| 29 | + externalHref: string; |
| 30 | + externalLabel: string; |
| 31 | + backHref: string; |
| 32 | + backLabel: string; |
| 33 | + logo?: string; |
| 34 | + role?: string; |
| 35 | + metrics?: Array<[string, string]>; |
| 36 | + steps: DetailStep[]; |
| 37 | + previous?: { href: string; label: string }; |
| 38 | + next?: { href: string; label: string }; |
| 39 | + externalIcon?: ReactNode; |
| 40 | +}; |
| 41 | + |
| 42 | +const stepIcons = { |
| 43 | + problem: PiMagnifyingGlass, |
| 44 | + reasoning: PiLightbulb, |
| 45 | + solution: PiWrench, |
| 46 | + result: PiCheckCircle, |
| 47 | +}; |
| 48 | + |
| 49 | +export default function DetailPage({ |
| 50 | + category, |
| 51 | + title, |
| 52 | + subtitle, |
| 53 | + intro, |
| 54 | + accent, |
| 55 | + highlight, |
| 56 | + externalHref, |
| 57 | + externalLabel, |
| 58 | + backHref, |
| 59 | + backLabel, |
| 60 | + logo, |
| 61 | + role, |
| 62 | + metrics, |
| 63 | + steps, |
| 64 | + previous, |
| 65 | + next, |
| 66 | + externalIcon, |
| 67 | +}: DetailPageProps) { |
| 68 | + return <main className="detail-page" style={{ '--detail-accent': accent } as CSSProperties}> |
| 69 | + <nav className="detail-nav" aria-label="详情页导航"> |
| 70 | + <a className="detail-back" href={backHref}><PiArrowLeftBold aria-hidden="true" />{backLabel}</a> |
| 71 | + <Link className="detail-brand" href="/">ZHEWEN TAN</Link> |
| 72 | + <a className="detail-external-mini" href={externalHref} target="_blank" rel="noopener noreferrer">{externalLabel}<PiArrowUpRightBold aria-hidden="true" /></a> |
| 73 | + </nav> |
| 74 | + |
| 75 | + <section className="detail-hero"> |
| 76 | + <div className="detail-hero-main"> |
| 77 | + <p className="detail-kicker">{category}{role ? ` · ${role}` : ''}</p> |
| 78 | + <div className="detail-title-row"> |
| 79 | + {logo && <span className="detail-logo"><img src={logo} alt="" /></span>} |
| 80 | + <div><small>{subtitle}</small><h1>{title}</h1></div> |
| 81 | + </div> |
| 82 | + <p className="detail-intro">{intro}</p> |
| 83 | + <div className="detail-actions"> |
| 84 | + <a href="#story">查看完整思路 <PiArrowDownBold aria-hidden="true" /></a> |
| 85 | + <a href={externalHref} target="_blank" rel="noopener noreferrer">{externalIcon ?? <PiGithubLogo aria-hidden="true" />}{externalLabel}<PiArrowUpRightBold aria-hidden="true" /></a> |
| 86 | + </div> |
| 87 | + </div> |
| 88 | + <aside className="detail-highlight"><small>KEY TAKEAWAY</small><strong>{highlight}</strong></aside> |
| 89 | + </section> |
| 90 | + |
| 91 | + {metrics && metrics.length > 0 && <section className="detail-metrics" aria-label="项目指标"> |
| 92 | + {metrics.map(([label, value]) => <div key={label}><span>{label}</span><strong>{value}</strong></div>)} |
| 93 | + </section>} |
| 94 | + |
| 95 | + <section className="detail-story" id="story"> |
| 96 | + <header><span>ENGINEERING STORY</span><h2>从问题到结果</h2><p>按真实工作顺序展开,而不是只展示一个最终结论。</p></header> |
| 97 | + <div className="detail-flow"> |
| 98 | + {steps.map((step, index) => { |
| 99 | + const Icon = stepIcons[step.icon]; |
| 100 | + return <div className="detail-flow-item" key={step.label}> |
| 101 | + <article className="detail-step"> |
| 102 | + <div className="detail-step-top"><span>{String(index + 1).padStart(2, '0')}</span><Icon aria-hidden="true" /></div> |
| 103 | + <small>{step.label}</small> |
| 104 | + <h3>{step.title}</h3> |
| 105 | + <p>{step.copy}</p> |
| 106 | + </article> |
| 107 | + {index < steps.length - 1 && <span className="detail-connector" aria-hidden="true"><PiArrowRightBold /></span>} |
| 108 | + </div>; |
| 109 | + })} |
| 110 | + </div> |
| 111 | + </section> |
| 112 | + |
| 113 | + <section className="detail-final-cta"> |
| 114 | + <div><small>UPSTREAM / PROJECT</small><h2>继续查看公开项目</h2><p>站内页面解释我的工作,外部链接保留项目原始上下文与最新进展。</p></div> |
| 115 | + <a href={externalHref} target="_blank" rel="noopener noreferrer">{externalIcon ?? <PiGithubLogo aria-hidden="true" />}{externalLabel}<PiArrowUpRightBold aria-hidden="true" /></a> |
| 116 | + </section> |
| 117 | + |
| 118 | + <nav className="detail-pagination" aria-label="详情页切换"> |
| 119 | + {previous ? <a href={previous.href}><PiArrowLeftBold aria-hidden="true" /><span><small>上一个</small>{previous.label}</span></a> : <span />} |
| 120 | + {next ? <a href={next.href}><span><small>下一个</small>{next.label}</span><PiArrowRightBold aria-hidden="true" /></a> : <span />} |
| 121 | + </nav> |
| 122 | + </main>; |
| 123 | +} |
0 commit comments