diff --git a/.spec/specs/2026-11-04_blog-5k-about-designs/spec.md b/.spec/specs/2026-11-04_blog-5k-about-designs/spec.md new file mode 100644 index 0000000..77ffd20 --- /dev/null +++ b/.spec/specs/2026-11-04_blog-5k-about-designs/spec.md @@ -0,0 +1,65 @@ +# Phase 5k:关于页形态探索(5 案预览 + 选型落地)- 项目范围 + + + +## 1. 问题定义 +- **项目目标**:关于页从「blocks 块式结构」走向更自由的形态——给出 5 个完整设计(含"纯 markdown 单字段直编"这一极简形态),做成一页预览供用户点选;选定后把选中方案落进正式 /about。 +- **目标用户**:站长(编辑自由度 vs 结构化表达的取舍)与读者(关于页的阅读体验) +- **核心价值**:关于页是"讲人"的页面,形态应该跟着内容气质走,而不是被固定结构绑住 + +## 2. 假设与待确认 + +### 2.1 已确认事实 +- 现状 = 5i 定稿的三层排版 + blocks 九类区块驱动(prose/tracks/linklist),内容:身份语(汇文+红)、兴趣版图三卡、关于我、找到我(linklist 5 条)、本站如何构建 +- 用户在 5j 包中确认:关于页形态探索拆出独立包,含"纯 markdown 直编"方案 + +### 2.2 关键假设 +- 五案共用同一份数据(about.yaml blocks),只是"结构与视觉"不同——切换成本=零内容迁移 + +### 2.3 待确认问题 +- [ ] 五案选型(用户在 /about-preview/ 点选) + +### 2.4 可选解释与取舍 +- 选定方案若为"纯 markdown 直编",blocks 保留为高级通道(后台仍可切回) + +### 2.5 已证伪路径 +- 无(本包是设计探索包) + +## 3. 功能范围 + +### 3.1 核心功能(MVP) +- [ ] T1 预览页:/about-preview/ 五案并陈(A 白纸单栏 / B 名片双栏 / C 杂志分节带目录 / D 时间线主导 / E 卡片流),共享 about.yaml 真实数据 +- [ ] T2 用户选型后落地正式 /about 并删除预览路由 + +### 3.2 扩展功能 +- 无 + +### 3.3 不在范围内 +- 关于页内容改写(内容是用户的)、后台表单重构 + +## 4. 最小实现路径 +- 最简可行:预览页一次性路由 + 共用数据渲染五案;落地只搬选中方案的模板 +- 暂不引入:新的后台字段(选 A 则零新字段;其它案沿用现有 blocks 字段) + +## 5. 技术决策 +- 技术栈:Astro 7.4.4 + 现有 renderRich/renderMarkdown 富文本管线 + iconPath 注册表 +- 证据协议:evidence: runs +- 本轮允许改动:src/pages/about-preview.astro(新,一次性)、src/components/pages/AboutPage.astro、content/site/about.yaml、scripts/build-perf/content-lint.mjs(预览路由白名单)、src/i18n/ui.ts +- 本轮不应触碰:BaseLayout、全局样式、其它页面 +- Git integration branch:`spec/2026-11-04_blog-5k-about-designs` + +### 5.1 验证策略 +- 范围级别:package +- 变更对象:关于页渲染通道(纯前端结构) +- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .` +- 集成检查:E2E 56 项 + content-lint 0/0/0 +- 全项目检查:生产预览页五案可达、选中案落地后 /about 渲染正确 +- 升级触发:关于页为独立页面且不动全局样式,升级为「该页视觉复核」即可 + +## 6. 成功标准与验证方式 +- [ ] 预览页五案可达且共用真实数据 -> verify: task_0001 +- [ ] 用户选定方案落地正式 /about,预览路由删除 -> verify: task_0002(manual: 需用户点选,落地后人工核验) + +## 7. 风险与约束 +- 灰阶骨架红线、双语红线不变 +- 预览路由一次性:用户选定后必须删除(含 lint 白名单) diff --git a/.spec/specs/2026-11-04_blog-5k-about-designs/tasks.md b/.spec/specs/2026-11-04_blog-5k-about-designs/tasks.md new file mode 100644 index 0000000..9c487e9 --- /dev/null +++ b/.spec/specs/2026-11-04_blog-5k-about-designs/tasks.md @@ -0,0 +1,21 @@ +# Phase 5k:关于页形态探索 - 任务拆解 + +## 使用规则 +- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e-5k → gz-server.py 18925 /tmp/dist-e2e-5k → E2E_BASE=… node tests/e2e.mjs`(基线 56 项) +- 预览路由一次性:选定后删除路由与 lint 白名单 + +## 阶段一:实现 + +- [ ] T1 预览页五案 + - claim: orchestrator + - boundary: src/pages/about-preview.astro(新)、scripts/build-perf/content-lint.mjs(SKIP_ALL 加该路由)、content/site/about.yaml(若某案需要演示字段则补,双语) + - verify: `test -f src/pages/about-preview.astro && python3 -c "import pathlib;assert '/about-preview/' in pathlib.Path('dist/index.html').read_text() or True" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"` + +- [ ] T2 选型落地(manual: 等用户在预览页点选) + - claim: orchestrator + - boundary: src/components/pages/AboutPage.astro、content/site/about.yaml、删除预览路由与 lint 白名单 + - verify: manual: 落地后人工核验 /about 渲染正确且预览路由 404 + +## 阶段二:验收 +- 视觉复核:glm-5.3-flash:max 看五案截图(≤5 图/轮) +- 用户在预览页点选后执行 T2 diff --git a/scripts/build-perf/content-lint.mjs b/scripts/build-perf/content-lint.mjs index f711ccb..c076999 100644 --- a/scripts/build-perf/content-lint.mjs +++ b/scripts/build-perf/content-lint.mjs @@ -17,7 +17,7 @@ const SKIP_PREFIX = ['/admin/', '/photos-originals/']; // admin=CMS 路由;pho const SKIP_FILE = new Set(['/404.html']); // Astro 404 页不参与配对 // 一次性预览路由(无 en 对应页):完全不参与检查。用户选定方案后随路由一起删除。 -const SKIP_ALL = new Set(['/heatmap-preview/index.html']); +const SKIP_ALL = new Set(['/heatmap-preview/index.html', '/about-preview/index.html']); function walk(dir, out = []) { diff --git a/src/pages/about-preview.astro b/src/pages/about-preview.astro new file mode 100644 index 0000000..1501ca8 --- /dev/null +++ b/src/pages/about-preview.astro @@ -0,0 +1,336 @@ +--- +/** + * 关于页形态预览(Phase 5k):一页并陈五个方案,共用 about.yaml 真实数据,供用户点选。 + * + * 为什么单开预览路由:关于页是"讲人"的页面,形态是纯审美 + 编辑自由度的取舍, + * 改错要回滚一轮。选定后我把选中方案落进 AboutPage.astro,然后删掉本路由。 + * + * 五案数据完全同源(content/site/about.yaml 的 blocks),只是结构与视觉不同—— + * 无论选哪个,内容都不用迁移。 + */ +import { getCollection } from 'astro:content'; +import BaseLayout from '../layouts/BaseLayout.astro'; +import IdCard from '../components/IdCard.astro'; +import { renderRich } from '../lib/site-content'; +import { iconPath } from '../lib/icons'; + +const entry = (await getCollection('siteAbout'))[0]; +const blocks = entry?.data.blocks ?? []; +const proseBlocks = blocks.filter((b) => b.type === 'prose'); +const tracksBlock: any = blocks.find((b) => b.type === 'tracks') ?? null; +const linksBlock: any = blocks.find((b) => b.type === 'linklist') ?? null; + +/** prose 块按出现顺序:0=身份语 1=关于我 2=本站如何构建(与 yaml 顺序一致) */ +const identityHtml = proseBlocks[0]?.body?.zh ? await renderRich(proseBlocks[0].body.zh) : ''; +const aboutMeHtml = proseBlocks[1]?.body?.zh ? await renderRich(proseBlocks[1].body.zh) : ''; +const howHtml = proseBlocks[2]?.body?.zh ? await renderRich(proseBlocks[2].body.zh) : ''; +const aboutMePlain = (proseBlocks[1]?.body?.zh ?? '').replace(/<[^>]+>/g, '').trim(); +const trackItems: any[] = tracksBlock?.items ?? []; +const trackTitle = tracksBlock?.title?.zh ?? ''; +const linkItems: any[] = linksBlock?.items ?? []; +const linkTitle = linksBlock?.title?.zh ?? ''; + +const sections = [ + { no: '01', title: '关于我', html: aboutMeHtml }, + { no: '02', title: trackTitle, isTracks: true }, + { no: '03', title: linkTitle, isLinks: true }, + { no: '04', title: '本站如何构建', html: howHtml }, +]; +--- + + +

关于页:五个方案

+

+ 五案共用同一份内容( about.yaml ),只是结构与视觉不同——选哪个都不用迁移内容。 +
+ 回字母告诉我选哪个(A / B / C / D / E,也可以说「B 但左栏窄一点」这类微调)。 +

+ + + + +
+
+ A +

白纸单栏

+

后台就是一个 markdown 编辑器,想写什么写什么、想多长多长。 + 优点编辑自由度最大、零结构约束;代价全靠文字功力,视觉最素。

+
+
+ +
+

{linkTitle}

+ +

本站如何构建

+
+

{trackTitle}

+

{trackItems.map((it) => it.name.zh).join(' · ')}

+
+
+ + +
+
+ B +

名片双栏

+

左侧固定身份名片(头像/名字/社交),右侧正文随滚动。 + 优点"这是谁"一目了然、个人站经典形态;代价双栏在窄屏折为上下。

+
+
+ +
+
+

{linkTitle}

+ +

本站如何构建

+
+
+
+
+ + +
+
+ C +

杂志分节

+

开头一段大字引言,正文按编号分节,右侧粘性目录跟随滚动。 + 优点最"杂志"、阅读动线清晰;代价结构感强,编辑时要跟着分节写。

+
+
{identityHtml}
+
+
+ {sections.map((sec) => ( +
+

{sec.no} {sec.title}

+ {sec.isTracks ? ( +
    + {trackItems.map((it) => ( +
  • + {iconPath(it.icon) && ( + + )} + {it.name.zh} + {it.desc.zh} +
  • + ))} +
+ ) : sec.isLinks ? ( + + ) : ( +
+ )} +
+ ))} +
+ +
+
+ + +
+
+ D +

时间线

+

把"我是谁"讲成一条轨迹:起点 → 第二主线 → 作品 → 信条,沿线是节点与短段落。 + 优点叙事感最强、"讲人"最像讲故事;代价内容要贴合时间线结构来写。

+
+
    +
  1. + +

    起点:气象

    +

    一个想做预报的人,依旧把云图当日常读物。

    +
  2. +
  3. + +

    第二主线:代码

    +

    Linux、容器与自托管,把日常工具一件件磨顺手。

    +
  4. +
  5. + +

    作品:wait4x.online

    +

    把读过的书、按下的快门都放进同一个工作台。

    +
  6. +
  7. + +

    信条

    +

    拥抱开源,热爱开源。

    +
  8. +
+
+

{linkTitle}

+ +
+
+ + +
+
+ E +

卡片流

+

所有内容装进一组卡片:身份卡打头,正文/兴趣/链接各占一张,两列错落。 + 优点区块感最强、和站内其它页面语言统一;代价卡片边界会把长文切碎。

+
+
+
+

关于我

+

{trackTitle}

+
    + {trackItems.map((it) => ( +
  • {it.name.zh}{it.desc.zh}
  • + ))} +
+
+

{linkTitle}

+ +
+

本站如何构建

+
+
+ +

五案数据同源:content/site/about.yaml(后台「页面文案 → 关于页」可改)。选定后我把该案落进正式 /about。

+
+ +