From 2f7cbcf7bd61c21a6f7af11d1930e505b5dee0ec Mon Sep 17 00:00:00 2001 From: wait4xx Date: Mon, 2 Nov 2026 18:21:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(about):=20=E5=9D=97=E5=BC=8F=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=9E=84=E5=BB=BA=E5=99=A8=E2=80=94=E2=80=94=E4=B9=9D?= =?UTF-8?q?=E7=B1=BB=E5=8C=BA=E5=9D=97=20+=20=E4=B8=89=E7=BA=A7=E5=9B=9E?= =?UTF-8?q?=E8=90=BD=E9=9B=B6=E5=9B=9E=E5=BD=92=EF=BC=885g=20T1/T2?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schema:siteAbout 增 layout{showIdCard} + blocks(z.discriminatedUnion 九类:prose/tracks/cards/timeline/quote/figure/linklist/cta/divider, 图片字段走 Astro image());对未知块型用 z.union([knownUnion, z.looseObject({type})]) 复合——已知块严格校验,未知块落宽松分支再由 访问器 WARN+丢弃(容错与严格校验的张力取舍) - page-blocks.ts:enabled:false 过滤、未知 type WARN+skip、pickBi 回落 zh 并 WARN、上限(块≤24/单块文本≤4000,超限截断并 WARN) - BlockRenderer 统一外壳 `
`(E2E 抓手)+ 九个块组件(样式自 AboutPage 原 diff --git a/src/components/blocks/Cards.astro b/src/components/blocks/Cards.astro new file mode 100644 index 0000000..42917ec --- /dev/null +++ b/src/components/blocks/Cards.astro @@ -0,0 +1,75 @@ +--- +// cards 块:技能/资源卡(2 或 3 列;复用 .track-row 的边框与内距语言) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +{block.title &&

{block.title}

} +
    + {block.items.map((item) => { + const body = ( + <> + {item.tag && {item.tag}} + {item.title} + {item.desc &&

    {item.desc}

    } + + ); + return ( +
  • + {item.href ? ( + {body} + ) : ( + + )} +
  • + ); + })} +
+ + diff --git a/src/components/blocks/Cta.astro b/src/components/blocks/Cta.astro new file mode 100644 index 0000000..76825ed --- /dev/null +++ b/src/components/blocks/Cta.astro @@ -0,0 +1,48 @@ +--- +// cta 块:单按钮行动点(克制:同屏最多 1 个,由编辑纪律保证) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +const isExternal = block.href.startsWith('http'); +--- + +
+ {block.text &&

{block.text}

} + + {block.label} + +
+ + diff --git a/src/components/blocks/Divider.astro b/src/components/blocks/Divider.astro new file mode 100644 index 0000000..4e1bce0 --- /dev/null +++ b/src/components/blocks/Divider.astro @@ -0,0 +1,20 @@ +--- +// divider 块:节奏控制(line = hairline 分隔;space = 纯留白) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +{block.style === 'space' ? :
} + + diff --git a/src/components/blocks/Figure.astro b/src/components/blocks/Figure.astro new file mode 100644 index 0000000..c5df9ab --- /dev/null +++ b/src/components/blocks/Figure.astro @@ -0,0 +1,38 @@ +--- +// figure 块:图片(Astro 图片管线,宽度自适应;caption 图注) +import { Image } from 'astro:assets'; +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +
+ {block.alt} + {block.caption &&
{block.caption}
} +
+ + diff --git a/src/components/blocks/LinkList.astro b/src/components/blocks/LinkList.astro new file mode 100644 index 0000000..8729a18 --- /dev/null +++ b/src/components/blocks/LinkList.astro @@ -0,0 +1,58 @@ +--- +// linklist 块:外链清单(比裸 markdown 链接可控:带 note 与统一行距) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; + +const isExternal = (href: string) => href.startsWith('http'); +--- + +{block.title &&

{block.title}

} + + + diff --git a/src/components/blocks/Prose.astro b/src/components/blocks/Prose.astro new file mode 100644 index 0000000..a3eb237 --- /dev/null +++ b/src/components/blocks/Prose.astro @@ -0,0 +1,46 @@ +--- +// prose 块:自由正文(markdown 已由读取层渲染为 HTML,净化在 site-content 的插件链) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +{block.title &&

{block.title}

} +
+ + diff --git a/src/components/blocks/Quote.astro b/src/components/blocks/Quote.astro new file mode 100644 index 0000000..3d13e0e --- /dev/null +++ b/src/components/blocks/Quote.astro @@ -0,0 +1,36 @@ +--- +// quote 块:引文(复用 .quote-line 语言 + 楷体) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +
+

{block.text}

+ {block.cite && — {block.cite}} +
+ + diff --git a/src/components/blocks/Timeline.astro b/src/components/blocks/Timeline.astro new file mode 100644 index 0000000..17ed981 --- /dev/null +++ b/src/components/blocks/Timeline.astro @@ -0,0 +1,66 @@ +--- +// timeline 块:经历/项目时间线(竖线语言,仅用既有 token) +import type { ResolvedBlock } from '../../lib/page-blocks'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +{block.title &&

{block.title}

} +
    + {block.items.map((item) => ( +
  1. + {item.time} +
    + {item.title} + {item.desc &&

    {item.desc}

    } +
    +
  2. + ))} +
+ + diff --git a/src/components/blocks/Tracks.astro b/src/components/blocks/Tracks.astro new file mode 100644 index 0000000..3a900ea --- /dev/null +++ b/src/components/blocks/Tracks.astro @@ -0,0 +1,70 @@ +--- +// tracks 块:兴趣/能力版图(复用原 AboutPage 的 .track-row 视觉语言) +import type { ResolvedBlock } from '../../lib/page-blocks'; +import { iconPath } from '../../lib/icons'; + +interface Props { + block: Extract; +} +const { block } = Astro.props; +--- + +

{block.title}

+
    + {block.items.map((item) => ( +
  • + +
    + {item.name} + {item.desc &&

    {item.desc}

    } +
    +
  • + ))} +
+ + diff --git a/src/components/pages/AboutPage.astro b/src/components/pages/AboutPage.astro index 00835f0..dbc1af6 100644 --- a/src/components/pages/AboutPage.astro +++ b/src/components/pages/AboutPage.astro @@ -3,70 +3,86 @@ import BaseLayout from '../../layouts/BaseLayout.astro'; import IdCard from '../../components/IdCard.astro'; import { t } from '../../i18n/ui'; import type { Locale } from '../../lib/locale'; -import { aboutIntroMarkdown, renderMarkdown } from '../../lib/site-content'; +import { aboutIntroMarkdown, pageTypography, renderMarkdown } from '../../lib/site-content'; +import { getAboutPage } from '../../lib/page-blocks'; +import BlockRenderer from '../blocks/BlockRenderer.astro'; // /en/ 侧由 T4 薄页传 locale='en';zh 路由无人传参,默认 zh 零回归 interface Props { locale?: Locale; } const { locale = 'zh' } = Astro.props; -// 身份陈述段改由 content/site/about.yaml 供给(多段 markdown,后台可改),缺值回落默认 -const introHtml = await renderMarkdown(await aboutIntroMarkdown(locale)); +// 三级回落(零回归):blocks 驱动 → intro markdown → 内置默认文案 +const { blocks, showIdCard } = await getAboutPage(locale); +const introHtml = + blocks.length === 0 ? await renderMarkdown(await aboutIntroMarkdown(locale)) : ''; +const typo = await pageTypography('siteAbout', 'about'); ---

{t('about_title', locale)}

-
+
+ {blocks.length > 0 ? ( + <> + {blocks.map((block) => )} + {showIdCard && } + + ) : ( + <> +
- + -
-

{t('about_tracks_title', locale)}

-
    -
  • - -
    - {t('about_track_reading_name', locale)} -

    {t('about_track_reading_desc', locale)}

    -
    -
  • -
  • - -
    - {t('about_track_tech_name', locale)} -

    {t('about_track_tech_desc', locale)}

    -
    -
  • -
  • - -
    - {t('about_track_photo_name', locale)} -

    {t('about_track_photo_desc', locale)}

    -
    -
  • -
-
+
+

{t('about_tracks_title', locale)}

+
    +
  • + +
    + {t('about_track_reading_name', locale)} +

    {t('about_track_reading_desc', locale)}

    +
    +
  • +
  • + +
    + {t('about_track_tech_name', locale)} +

    {t('about_track_tech_desc', locale)}

    +
    +
  • +
  • + +
    + {t('about_track_photo_name', locale)} +

    {t('about_track_photo_desc', locale)}

    +
    +
  • +
+
-
-

{t('about_build_title', locale)}

-

{t('about_build_desc', locale)}

-
+
+

{t('about_build_title', locale)}

+

{t('about_build_desc', locale)}

+
+ + )} +