feat(cms): 后台扩展五件——关于页 markdown 化/社交/头像/favicon/分轨首页

- T1 关于页=markdown 文档:about.yaml intro 扩为多段 markdown(zh/en),
  AboutPage 用 @astrojs/markdown-remark 的 createMarkdownProcessor(进程
  内单例复用)+ set:html 渲染,正文 p/ul/strong/a 对齐站点排版;名片与
  技能卡结构不动
- T2 社交链接单一数据源:home.yaml socials[](label/url/icon)+
  SocialLinks.astro(图标注册表白名单:github/rss/email/bilibili/x,
  未登记 icon 回退纯文字),首页 zh/en 内联硬编码社交块删除
- T3 头像与 favicon 可上传:schema avatar: image()(Astro Image 出 60px
  WebP,未设回退渐变 SVG);home.yaml favicon → BaseLayout link[rel=icon]
  按扩展名给 type,留空回退 /favicon.svg
- T4 首页分轨:PostList perTrack → 阅读轨/技术轨两段各限 6,各自出口
  (阅读→/books/ 技术→/archives/,en 同构);E2E 断言改为两段各≤6+各自
  出口+阅读在前
- T5 后台列表日期:posts summary「{{pubDate}} · {{title}}」、moments
  「{{fields.pubDate}} · {{fields.text}}」+sortable;photos 无 frontmatter
  日期(JSON sidecar)保持分组 · 标题并在文档写明原因
- 顺手修 content-lint 漏报:中文文件名在 HTML 里为 %XX 编码,脚本直接
  拼盘上路径导致 6 条"缺图"误报 → 加 decodeURI
- 清理:删除书封误传遗留的双重嵌套目录文件

车道:op-gitea(5/5,build+E2E 37/37+lint 47 页 0/0/0);收编复验:
build 46 页 + E2E 37/37 连跑(首跑两红为车道并发构建擦 dist 的瞬时态,
复跑绿)

验证: node v26.13.0 + astro 7.4.4 全量构建 46 页 exit 0;E2E 37/37;
      content-lint 0/0/0;books details/命令面板 playwright 实测

Spec: none (后台能力扩展,车道级交付)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-02 15:46:41 +08:00
1 parent 227bc24797
commit 328ab68f18
16 files changed
+409 -146

No files matched your search

+48 -30
View File
@@ -1,42 +1,60 @@
---
// 名片头像占位:内联 SVG 渐变 60px;真实头像接入后整体替换本组件即可
// 名片头像:后台「主页设置 → 头像」上传后在 content/site/assets/ 落图,
// 经 Astro 图片管线出 60px WebP;未配置则回退内置渐变占位(历史视觉不变)
import { Image } from 'astro:assets';
import { homeAvatar } from '../lib/site-content';
const src = await homeAvatar();
---
<svg
class="avatar"
viewBox="0 0 60 60"
width="60"
height="60"
role="img"
aria-label="头像占位"
>
<defs>
<linearGradient id="avatar-grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="var(--mood-3)"></stop>
<stop offset="1" stop-color="var(--accent)"></stop>
</linearGradient>
</defs>
<rect width="60" height="60" rx="14" fill="url(#avatar-grad)"></rect>
<circle cx="44" cy="15" r="10" fill="#fff" opacity=".25"></circle>
<path d="M0 46 20 28 34 40 46 32 60 46 60 60 0 60Z" fill="#000" opacity=".14"></path>
<text
x="30"
y="38"
text-anchor="middle"
font-size="16"
font-weight="700"
fill="#fff"
opacity=".92"
font-family="var(--font-mono)"
{src ? (
<Image
class="avatar"
src={src}
alt="wait4x 头像"
width={60}
height={60}
format="webp"
quality={82}
/>
) : (
<svg
class="avatar"
viewBox="0 0 60 60"
width="60"
height="60"
role="img"
aria-label="头像占位"
>
w4
</text>
</svg>
<defs>
<linearGradient id="avatar-grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="var(--mood-3)"></stop>
<stop offset="1" stop-color="var(--accent)"></stop>
</linearGradient>
</defs>
<rect width="60" height="60" rx="14" fill="url(#avatar-grad)"></rect>
<circle cx="44" cy="15" r="10" fill="#fff" opacity=".25"></circle>
<path d="M0 46 20 28 34 40 46 32 60 46 60 60 0 60Z" fill="#000" opacity=".14"></path>
<text
x="30"
y="38"
text-anchor="middle"
font-size="16"
font-weight="700"
fill="#fff"
opacity=".92"
font-family="var(--font-mono)"
>
w4
</text>
</svg>
)}
<style>
.avatar {
flex: none;
border-radius: 14px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
object-fit: cover;
}
</style>