feat(cms): 内容管理闭环——站点单例三件套+文章视频字段

- A 站点文案单例:content/site/{home,about,opensource}.yaml +
  src/lib/site-content.ts 访问层(字段级回落默认,默认值=原文案逐字
  一致,零回归);接线主页 tagline(en 首页新增该块)、关于页 intro、
  开源页叙述三段 + PINNED 参数化(projects.ts: loadProjects(pinned)
  /orderPinned,PINNED_DEFAULT 仅缺省兜底)
- loader 语义修正(返工如实入档):Astro 7 的 file() 把 YAML 顶层对象
  按 Object.entries 拆成多条目(id=key、data=value),单例字段必炸
  schema;改 glob 精确到单文件 = 真单例语义,data 即文件根对象,
  Sveltia 可编辑根字段零嵌套(比包 main: 层的方案后台体验更好)
- B 文章媒体:posts +video/+poster;Sveltia file 字段
  (media_folder=public/media、public_folder=/media、hint ≤50MB);
  正文后渲染 video controls preload=metadata playsinline,无则不渲染;
  图片链路核对本已一致(./assets 走 Astro 管线);链接 markdown 原生
- docs/写作指南.md 补「后台上传」节(三类媒体 + 三单例操作 + 体积纪律)

Sveltia 集合:posts / moments / photos / site_pages(三单例)

车道:op-gitea(实测 tagline 改 yaml 产物联动、置空回落默认、pinned
改名排序变、视频临时件渲染全对、构建绿 + lint 45 页 0/0/0);收编
复验:构建 44 页 + E2E 37/37 + 三 yaml 扁平键就位

验证: pnpm astro build 44 页 exit 0;E2E 37/37 PASS;content-lint
      0/0/0;播放器/单例/置顶 playwright 实测全过

Spec: none (内容管理闭环,车道级交付)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-02 12:42:10 +08:00
1 parent 5be09b6f32
commit 264148f0a6
15 files changed
+346 -12

No files matched your search

+31
View File
@@ -15,6 +15,9 @@ export const POST_TRACKS = {
export type PostCategory = keyof typeof POST_TRACKS;
export type PostTrack = (typeof POST_TRACKS)[PostCategory];
/** 双语文案字段:两语言各自可选,缺一回落一(回退逻辑在 src/lib/site-content.ts) */
const bilingual = z.object({ zh: z.string().optional(), en: z.string().optional() });
export function trackOf(category: PostCategory): PostTrack {
return POST_TRACKS[category];
}
@@ -30,6 +33,10 @@ const postsSchema = z.object({
description: z.string().optional(),
/** 原书作者(书架卡署名) */
bookAuthor: z.string().optional(),
/** 文章附视频(单文件):存 public/media,frontmatter 写 /media/<name>.mp4 */
video: z.string().optional(),
/** 视频封面(可选):同 public/media 下的图片 URL */
poster: z.string().optional(),
draft: z.boolean().optional(),
});
@@ -63,6 +70,30 @@ const _exifShapeOk: _PhotoExifOut extends PhotoExif
: never = true;
export const collections = {
// 站点文案单例(后台「页面文案」可改):一文件一集合
// 用 glob(pattern 精确到单文件) 而非 file():Astro 的 file() 把 YAML 顶层对象当“条目映射”
// (每个顶层键各自成一条 entry,见 astro/dist/content/loaders/file.js),根级字段会被拆碎;
// glob 每文件一条 entry(id=文件名)、data=文件根对象,正是单例语义且 Sveltia 可直接编根字段
// 双语文案用 {zh,en} 对象字段(各自可选):任一语言空/缺字段 → src/lib/site-content.ts 回落默认
siteHome: defineCollection({
loader: glob({ pattern: 'home.yaml', base: './content/site' }),
schema: z.object({ tagline: bilingual.optional() }),
}),
siteAbout: defineCollection({
loader: glob({ pattern: 'about.yaml', base: './content/site' }),
schema: z.object({ intro: bilingual.optional() }),
}),
siteOpenSource: defineCollection({
loader: glob({ pattern: 'opensource.yaml', base: './content/site' }),
schema: z.object({
blurb: bilingual.optional(),
blurb_after: bilingual.optional(),
repo_url: z.string().optional(),
repo_label: z.string().optional(),
/** 置顶仓短名,顺序即展示顺序(原 projects.ts 硬编码名单迁于此) */
pinned: z.array(z.string()).optional(),
}),
}),
// slug 即文件名(glob loader 默认按文件名生成 id)
posts: defineCollection({
loader: glob({ pattern: '**/*.md', base: './content/posts' }),