- 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
161 lines
5.2 KiB
Plaintext
161 lines
5.2 KiB
Plaintext
---
|
||
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>
|