Files
blog/src/components/pages/AboutPage.astro
T
wait4xandGLM-5.3 2f7cbcf7bd feat(about): 块式页面构建器——九类区块 + 三级回落零回归(5g T1/T2)
- 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 统一外壳 `<section class="blk" data-block={type}>`(E2E
  抓手)+ 九个块组件(样式自 AboutPage 原 <style> 搬迁,只吃既有 token)
- AboutPage 三级回落:blocks → intro markdown → 内置默认;legacy
  tracks/build 段整体挪入回退分支(文案逐字一致,零回归)
- 图标白名单 src/lib/icons.ts(12 键,未登记回退首字占位)
- 后台 about 单例四段结构(blocks/layout/intro/typography),块为
  list+types+typeKey,每块带「显示」开关与中文标签

协调报备:车道修了一处跨车道插入事故(T3 把 typography 插进了 intro
的 widget 内导致字段错位),重写为四段正确结构;并发构建擦 dist 的
假失败改用「build→快照 dist→快照起服务跑 E2E」规避(37/37 稳定)

验证: 构建 48 页 exit 0;E2E 37/37(快照口径);九类块顺序与配置一致、
      figure 走 /_astro/*.webp 管线、未知块与外链图被拦并 WARN、
      终版 3 块零回归、en 同构、admin about 四段字段齐全

Spec: 2026-11-02_blog-5g-pages (T1/T2)

Co-authored-by: GLM-5.3
2026-11-02 18:21:33 +08:00

161 lines
5.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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, 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;
// 三级回落(零回归):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');
---
<BaseLayout title={`${t('about_title', locale)} · wait4x`}>
<h1 class="page-title">{t('about_title', locale)}</h1>
<div class={typo.className || undefined} style={typo.style || undefined}>
{blocks.length > 0 ? (
<>
{blocks.map((block) => <BlockRenderer block={block} />)}
{showIdCard && <IdCard />}
</>
) : (
<>
<div class="intro rich" set:html={introHtml} />
<IdCard />
<section class="tracks" aria-label={t('about_tracks_title', locale)}>
<h2 class="sec-title">{t('about_tracks_title', locale)}</h2>
<ul>
<li class="track-row">
<span class="track-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 4h6a4 4 0 0 1 4 4v13a3 3 0 0 0-3-3H2z"></path>
<path d="M22 4h-6a4 4 0 0 0-4 4v13a3 3 0 0 1 3-3h7z"></path>
</svg>
</span>
<div>
<strong>{t('about_track_reading_name', locale)}</strong>
<p>{t('about_track_reading_desc', locale)}</p>
</div>
</li>
<li class="track-row">
<span class="track-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="m4 5.5 6.5 6.5L4 18.5"></path>
<path d="M12.5 18.5H20"></path>
</svg>
</span>
<div>
<strong>{t('about_track_tech_name', locale)}</strong>
<p>{t('about_track_tech_desc', locale)}</p>
</div>
</li>
<li class="track-row">
<span class="track-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
<circle cx="12" cy="13" r="4"></circle>
</svg>
</span>
<div>
<strong>{t('about_track_photo_name', locale)}</strong>
<p>{t('about_track_photo_desc', locale)}</p>
</div>
</li>
</ul>
</section>
<section class="build" aria-label={t('about_build_aria', locale)}>
<h2 class="sec-title">{t('about_build_title', locale)}</h2>
<p>{t('about_build_desc', locale)}</p>
</section>
</>
)}
</div>
</BaseLayout>
<style>
.page-title {
font-size: var(--text-h1);
line-height: var(--leading-heading);
color: var(--ink-1);
margin-top: var(--space-5);
text-wrap: balance;
}
.intro {
font-size: 15.5px;
line-height: 1.75;
color: var(--ink-2);
max-width: var(--measure);
margin-top: var(--space-3);
}
/* markdown 正文内部元素(多段/列表/链接)与站点排版对齐 */
.intro :global(p) { margin-block: 0 var(--space-2); }
.intro :global(ul) {
margin: var(--space-1) 0 var(--space-2);
padding-left: 1.2em;
list-style: disc;
}
.intro :global(li) { margin-block: 0.2em; }
.intro :global(a) { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.intro :global(strong) { color: var(--ink-1); font-weight: 600; }
.sec-title {
font-size: 14px;
font-family: var(--font-mono);
color: var(--ink-3);
letter-spacing: 0.08em;
margin: var(--space-5) 0 var(--space-3);
}
.tracks ul {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: var(--space-2);
}
.track-row {
display: flex;
align-items: flex-start;
gap: var(--space-3);
border: 1px solid var(--line);
border-radius: 10px;
padding: var(--space-3) var(--space-4);
}
.track-icon {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: var(--bg-soft);
color: var(--ink-3);
}
.track-row strong {
font-size: 15px;
color: var(--ink-1);
}
.track-row p {
font-size: 14px;
color: var(--ink-3);
margin-top: 2px;
}
.build p {
font-size: 15px;
color: var(--ink-2);
max-width: var(--measure);
}
</style>