1035 字
5 分钟
封面与画廊规则详解
一、背景
博客最初设计时,文章详情页会自动提取文章第一张图片作为封面。但这种隐式逻辑存在以下问题:
- 无法手动指定喜欢的图片作为封面
- 画廊页面无法显示文章(因为没有显式的 image 字段)
- 推送 GitHub 时,部分空 image 字段导致构建失败
二、解决方案
2.1 BAT 脚本自动提取
修改 scripts/add-frontmatter.cjs,实现以下逻辑:
| 场景 | 处理方式 |
|---|---|
| 完全没有 frontmatter | 添加完整模板,自动提取第一张图片 |
| 已有 frontmatter 但无 image | 补充 image 字段,提取第一张图片 |
| 文章无图片 | 添加空的 image 字段 |
| 已有 image 字段 | 跳过,不重复处理 |
2.2 配置文件修复
修改 src/content/config.ts,允许 image 为空:
image: z.string().optional().default("").nullable(),关键点:.nullable() 让空字符串也能通过验证,不再导致构建失败。
三、画廊实现
3.1 页面结构
/gallery/ → src/pages/gallery/index.astro → src/components/GalleryPanel.astro3.2 显示逻辑
画廊页面只显示显式设置了 image 字段的文章:
const postsWithCover = posts.filter(post => post.data.image);3.3 文章详情页回退
如果没有设置 image,文章详情页会自动提取第一张图片:
function extractFirstImageFromMarkdown(markdown) { const imgMatch = markdown.match(/!\[.*?\]\(([^)]+)\)/i); return imgMatch ? imgMatch[1] : undefined;}四、使用流程
4.1 写文章
- 在 Obsidian 中新建 md 文件
- 写入文章内容(建议在开头插入一张图片)
- 保存文件
4.2 运行 BAT 脚本
- 打开
博客升级版bat.bat - 选择 选项2(生成 YAML 模板)
- 脚本会自动:
- 添加/补全 frontmatter
- 提取文章第一张图片作为封面
- 在控制台显示处理结果
4.3 查看效果
⚠️ 注意:Obsidian 有缓存,修改后需要:
- 按
Ctrl+S保存- 关闭文件重新打开
- 或刷新编辑器
五、注意事项
5.1 图片格式
必须是 Markdown 图片格式:
5.2 推送问题
修复前:空 image 导致构建失败
修复后:.nullable() 允许空值,构建正常
5.3 画廊显示
- 有 image 字段 → 画廊显示 ✅
- 无 image 字段 → 画廊不显示,但文章页会自动提取第一张图 ✅
六、相关文件路径
| 功能 | 文件路径 |
|---|---|
| 脚本 | scripts/add-frontmatter.cjs |
| 配置 | src/content/config.ts |
| 画廊组件 | src/components/GalleryPanel.astro |
| 画廊页面 | src/pages/gallery/index.astro |
| 封面提取 | src/components/PostPage.astro |
七、攻坚过程复盘(2026-04-22 凌晨)
7.1 问题背景
从今天早上开始,我们持续攻坚封面自动提取功能,历经多次挫折才最终解决。
7.2 遇到的困难
| 阶段 | 问题 | 后果 |
|---|---|---|
| 初期 | Obsidian 缓存导致看不到变化 | 误以为脚本不生效 |
| 中期 | 空 image: 导致构建失败 | EdgeOne 部署报错 |
| 后期 | 图片提取逻辑错误 | 提取了 frontmatter 中的图片而非正文 |
7.3 失败原因分析
-
Obsidian 缓存问题
- 现象:脚本运行后,Obsidian 显示的文件内容没有变化
- 原因:Obsidian 有文件缓存,需要手动刷新或关闭重开
- 解决:增加提示信息,提醒用户刷新
-
构建失败问题
- 现象:
image: Expected type "string", received "null" - 原因:空
image:字段被解析为 null,而不是空字符串 - 解决:在 config.ts 中添加
.nullable()
- 现象:
-
图片提取错误
- 现象:提取的是 frontmatter 中的图片,不是文章正文的第一张图
- 原因:
extractFirstImage()函数直接匹配整个文件内容,没有忽略 frontmatter - 解决:新增
extractFirstImageIgnoreFrontmatter()函数,先解析出文章正文再提取图片
7.4 最终突破
// 正确的提取逻辑function extractFirstImageIgnoreFrontmatter(content) { // 1. 找到 frontmatter 结束的位置 const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/m); if (match && match[1]) { // 2. 只从正文内容中提取图片 const bodyContent = match[1]; return extractFirstImage(bodyContent); } // 3. 如果没有 frontmatter,直接提取 return extractFirstImage(content);}7.5 经验总结
- 调试时注意缓存:IDE/编辑器的缓存可能导致误判
- Zod 验证需考虑空值:
.nullable()可以解决空字符串验证问题 - 正则匹配要精确:提取内容时需明确范围,避免误提取
八、验证方法
- 新建测试文章,包含图片
- 运行 BAT 脚本选项2
- 检查文件是否添加
image:字段 - 访问
/gallery/查看是否显示 - 构建测试:
pnpm build
发现错误或想要改进这篇文章?
在 GitHub 上编辑此页