Repository navigation
feat: add cover image display for article detail and list pages #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # Design: Add Cover Image Display | ||
|
|
||
| ## Architecture Overview | ||
|
|
||
| 本变更涉及两个独立的 UI 组件修改,不引入新的架构模式。 | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────┐ | ||
| │ PostDetails.astro │ | ||
| │ ┌───────────────────────────────────────────────┐ │ | ||
| │ │ Cover Image (NEW) │ │ | ||
| │ │ - 条件渲染 (ogImageUrl 存在时) │ │ | ||
| │ │ - 响应式图片容器 │ │ | ||
| │ └───────────────────────────────────────────────┘ │ | ||
| │ ┌───────────────────────────────────────────────┐ │ | ||
| │ │ 导读区块 (existing) │ │ | ||
| │ └───────────────────────────────────────────────┘ │ | ||
| │ ┌───────────────────────────────────────────────┐ │ | ||
| │ │ 文章内容 (existing) │ │ | ||
| │ └───────────────────────────────────────────────┘ │ | ||
| └─────────────────────────────────────────────────────┘ | ||
|
|
||
| ┌─────────────────────────────────────────────────────┐ | ||
| │ Card.astro │ | ||
| │ ┌─────────┬────────────────────────────────────┐ │ | ||
| │ │ Thumb │ Title + Badge │ │ | ||
| │ │ (NEW) │ Datetime │ │ | ||
| │ │ │ Description │ │ | ||
| │ └─────────┴────────────────────────────────────┘ │ | ||
| │ ↑ 有 ogImage 时的布局 │ | ||
| │ │ | ||
| │ ┌───────────────────────────────────────────────┐ │ | ||
| │ │ Title + Badge │ │ | ||
| │ │ Datetime │ │ | ||
| │ │ Description │ │ | ||
| │ └───────────────────────────────────────────────┘ │ | ||
| │ ↑ 无 ogImage 时的布局(保持不变) │ | ||
| └─────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Data Flow | ||
|
|
||
| ### ogImage 数据类型 | ||
|
|
||
| 根据 `content.config.ts` 定义: | ||
| ```typescript | ||
| ogImage: image().or(z.string()).optional() | ||
| ``` | ||
|
|
||
| 可能的值: | ||
| 1. `undefined` - 未配置 | ||
| 2. `string` - 远程 URL(如 CDN 地址) | ||
| 3. `ImageMetadata` - 本地图片 asset(有 `.src` 属性) | ||
|
|
||
| ### PostDetails.astro 中已有的解析逻辑 | ||
|
|
||
| ```typescript | ||
| let ogImageUrl: string | undefined; | ||
|
|
||
| if (typeof initOgImage === "string") { | ||
| ogImageUrl = initOgImage; // 远程图片 | ||
| } else if (initOgImage?.src) { | ||
| ogImageUrl = initOgImage.src; // 本地 asset | ||
| } | ||
| ``` | ||
|
|
||
| 直接复用此变量即可。 | ||
|
|
||
| ### Card.astro 需要新增解析 | ||
|
|
||
| Card 组件当前未使用 ogImage,需要: | ||
| 1. 从 `data` 中解构 `ogImage` | ||
| 2. 添加类似的类型判断逻辑 | ||
|
|
||
| ## Styling Decisions | ||
|
|
||
| ### 文章详情页头图 | ||
|
|
||
| ``` | ||
| 容器: | ||
| - 宽度:100% | ||
| - 最大高度:400px(桌面)/ 250px(移动) | ||
| - 圆角:rounded-lg (8px) | ||
| - 下边距:mb-6 | ||
|
|
||
| 图片: | ||
| - object-fit: cover | ||
| - width: 100% | ||
| - height: auto | ||
| - loading: lazy | ||
| ``` | ||
|
|
||
| ### 文章列表缩略图 | ||
|
|
||
| ``` | ||
| 容器: | ||
| - 尺寸:80px × 80px(移动)/ 120px × 80px(桌面) | ||
| - 圆角:rounded-md (6px) | ||
| - flex-shrink: 0 | ||
|
|
||
| 图片: | ||
| - object-fit: cover | ||
| - 100% 填充容器 | ||
| - loading: lazy | ||
| ``` | ||
|
|
||
| ## Responsive Breakpoints | ||
|
|
||
| | Breakpoint | 详情页头图高度 | 列表缩略图 | | ||
| |------------|---------------|-----------| | ||
| | < 640px (sm) | max-h-[250px] | 80×80px,图片在标题上方 | | ||
| | ≥ 640px | max-h-[400px] | 120×80px,图片在左侧 | | ||
|
|
||
| ## Trade-offs | ||
|
|
||
| ### 选项 A:列表页图片在左侧 | ||
| - ✅ 视觉层次清晰 | ||
| - ✅ 符合常见博客布局 | ||
| - ❌ 需要调整现有 flex 结构 | ||
|
|
||
| ### 选项 B:列表页图片在上方 | ||
| - ✅ 实现简单 | ||
| - ❌ 占用更多垂直空间 | ||
| - ❌ 有图/无图的卡片高度差异大 | ||
|
|
||
| **决策**:采用选项 A,桌面端图片在左侧,移动端图片在上方。 | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| - 无 ogImage 的文章保持原有布局,无任何变化 | ||
| - 不修改 ogImage 的 schema 定义 | ||
| - 不影响 Open Graph 元数据生成 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Proposal: Add Cover Image Display | ||
|
|
||
| ## Change ID | ||
| `add-cover-image-display` | ||
|
|
||
| ## Summary | ||
| 在文章页面和文章列表中显示配置的头图(ogImage)。当前 ogImage 仅用于社交媒体分享时的 Open Graph 元数据,本提案将其扩展为可选的视觉展示元素。 | ||
|
|
||
| ## Motivation | ||
| - 用户配置了 ogImage 后,期望在文章页面中能直接看到这张图片 | ||
| - 头图可以增强文章的视觉吸引力,提升阅读体验 | ||
| - 在文章列表中显示缩略图可以帮助读者快速识别感兴趣的内容 | ||
|
|
||
| ## Scope | ||
|
|
||
| ### In Scope | ||
| 1. **文章详情页**:在"导读"区块上方显示头图 | ||
| 2. **文章列表卡片**:在 description 左侧显示头图缩略图 | ||
|
|
||
| ### Out of Scope | ||
| - 修改 ogImage 的 schema 定义 | ||
| - 修改 Open Graph 元数据生成逻辑 | ||
| - 添加新的配置选项(如全局开关) | ||
|
|
||
| ## Affected Files | ||
| - `src/layouts/PostDetails.astro` - 文章详情页布局 | ||
| - `src/components/Card.astro` - 文章列表卡片组件 | ||
|
|
||
| ## Dependencies | ||
| - 无外部依赖 | ||
| - 复用现有的 ogImage 解析逻辑 | ||
|
|
||
| ## Risks & Mitigations | ||
| | Risk | Impact | Mitigation | | ||
| |------|--------|------------| | ||
| | 图片加载失败 | 页面布局错乱 | 使用 CSS object-fit 和固定容器尺寸 | | ||
| | 外部图片 URL 可能较慢 | 页面加载体验下降 | 使用 loading="lazy" 延迟加载 | | ||
| | 响应式布局适配 | 移动端显示异常 | 使用 Tailwind 响应式类,移动端调整布局 | | ||
|
|
||
| ## Success Criteria | ||
| - [ ] 配置了 ogImage 的文章在详情页"导读"上方显示头图 | ||
| - [ ] 配置了 ogImage 的文章在列表卡片中 description 左侧显示缩略图 | ||
| - [ ] 未配置 ogImage 的文章保持原有布局不变 | ||
| - [ ] 响应式布局在移动端和桌面端均正常显示 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # Spec: Cover Image Display | ||
|
|
||
| ## Overview | ||
| 为配置了 ogImage 的文章提供页面内头图展示能力。 | ||
|
|
||
| --- | ||
|
|
||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: Post Detail Cover Image | ||
| 当文章配置了 ogImage 时,系统 SHALL 在文章详情页的"导读"区块上方显示头图。 | ||
|
|
||
| - 图片 SHALL 显示在标题、日期之后,"导读"区块之前 | ||
| - 图片宽度 SHALL 为 100%,高度自适应,最大高度受限 | ||
| - 系统 SHALL 使用 lazy loading 优化加载性能 | ||
| - 图片加载期间 SHALL NOT 影响页面布局 | ||
|
|
||
| #### Scenario: Article with ogImage displays cover | ||
| - **GIVEN** 一篇文章配置了 `ogImage: "https://example.com/cover.png"` | ||
| - **WHEN** 用户访问该文章详情页 | ||
| - **THEN** 应在"导读"区块上方看到该头图 | ||
| - **AND** 图片应为圆角样式,宽度撑满内容区域 | ||
|
|
||
| #### Scenario: Article without ogImage shows no cover | ||
| - **GIVEN** 一篇文章未配置 ogImage | ||
| - **WHEN** 用户访问该文章详情页 | ||
| - **THEN** 页面布局与当前保持一致,无头图区域 | ||
|
|
||
| --- | ||
|
|
||
| ### Requirement: Card Thumbnail Image | ||
| 当文章配置了 ogImage 时,系统 SHALL 在文章列表卡片中显示缩略图。 | ||
|
|
||
| - 缩略图 SHALL 显示在卡片左侧(桌面端)或上方(移动端) | ||
| - 系统 SHALL 使用固定尺寸容器,图片使用 object-cover 裁切 | ||
| - 系统 SHALL 使用 lazy loading 优化加载性能 | ||
| - 无 ogImage 的文章 SHALL 保持原有纯文字布局 | ||
|
|
||
| #### Scenario: Article with ogImage shows thumbnail in list | ||
| - **GIVEN** 首页或文章列表页 | ||
| - **AND** 某篇文章配置了 ogImage | ||
| - **WHEN** 该文章卡片被渲染 | ||
| - **THEN** 应在卡片左侧(桌面端)显示缩略图 | ||
| - **AND** 缩略图为固定尺寸、圆角样式 | ||
|
|
||
| #### Scenario: Article without ogImage shows text-only card | ||
| - **GIVEN** 首页或文章列表页 | ||
| - **AND** 某篇文章未配置 ogImage | ||
| - **WHEN** 该文章卡片被渲染 | ||
| - **THEN** 卡片布局保持原有样式,无缩略图区域 | ||
|
|
||
| --- | ||
|
|
||
| ### Requirement: Responsive Layout | ||
| 头图展示 SHALL 在不同屏幕尺寸下正常显示。 | ||
|
|
||
| - 移动端(<640px):详情页头图最大高度 SHALL 为 250px | ||
| - 移动端(<640px):列表缩略图 SHALL 显示在标题上方 | ||
| - 桌面端(≥640px):详情页头图最大高度 SHALL 为 400px | ||
| - 桌面端(≥640px):列表缩略图 SHALL 显示在内容左侧 | ||
|
|
||
| #### Scenario: Mobile view of article with cover | ||
| - **GIVEN** 用户使用移动设备(屏幕宽度 < 640px) | ||
| - **WHEN** 访问配置了 ogImage 的文章 | ||
| - **THEN** 详情页头图高度不超过 250px | ||
| - **AND** 列表页缩略图显示在标题上方 | ||
|
|
||
| #### Scenario: Desktop view of article with cover | ||
| - **GIVEN** 用户使用桌面设备(屏幕宽度 ≥ 640px) | ||
| - **WHEN** 访问配置了 ogImage 的文章 | ||
| - **THEN** 详情页头图高度不超过 400px | ||
| - **AND** 列表页缩略图显示在内容左侧 | ||
|
|
||
| --- | ||
|
|
||
| ## Related Capabilities | ||
| - Open Graph 元数据生成(不受影响) | ||
| - 动态 OG 图片生成(不受影响) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Tasks: Add Cover Image Display | ||
|
|
||
| ## Task List | ||
|
|
||
| ### Phase 1: 文章详情页头图展示 | ||
|
|
||
| - [x] **T1.1** 修改 `PostDetails.astro`,在标题上方添加头图展示区域 | ||
| - 条件渲染:仅当 `ogImageUrl` 存在时显示 | ||
| - 容器样式:圆角、阴影、浅色边框 | ||
| - 图片样式:object-cover、lazy loading | ||
| - hover 效果:阴影增强 + 轻微放大 | ||
| - 验证:本地预览确认有/无头图的文章显示正确 | ||
|
|
||
| ### Phase 2: 文章列表卡片头图展示 | ||
|
|
||
| - [x] **T2.1** 修改 `Card.astro`,获取 ogImage 数据 | ||
| - 从 props 中解构 ogImage 字段 | ||
| - 处理 string 和 image asset 两种类型 | ||
|
|
||
| - [x] **T2.2** 调整 Card 布局结构 | ||
| - 布局:标题行 → 日期行 → 头图+description 行 | ||
| - 有头图时:左侧图片 + 右侧内容(flex 布局) | ||
| - 无头图时:保持原有纯文字布局 | ||
| - 响应式:移动端图片在上,桌面端图片在左 | ||
|
|
||
| - [x] **T2.3** 添加缩略图样式 | ||
| - 尺寸:96px × 160px | ||
| - object-cover 裁切 | ||
| - 圆角边框 + 浅色边框 + 阴影 | ||
| - hover 效果:放大 5% | ||
| - lazy loading | ||
|
|
||
| ### Phase 3: RSS 头图支持 | ||
|
|
||
| - [x] **T3.1** 修改 `rss.xml.ts`,添加头图到 RSS feed | ||
| - 使用 `enclosure` 标签(RSS 2.0 标准) | ||
| - 自动识别图片 MIME type | ||
|
|
||
| ### Phase 4: 验证与测试 | ||
|
|
||
| - [x] **T4.1** 本地预览测试 | ||
| - 有 ogImage 的文章详情页 | ||
| - 无 ogImage 的文章详情页 | ||
| - 首页文章列表 | ||
| - 文章归档页面 | ||
|
|
||
| - [x] **T4.2** 响应式测试 | ||
| - 桌面端(>1024px) | ||
| - 移动端(<768px) | ||
|
|
||
| ## Dependencies | ||
| - T2.2 依赖 T2.1 | ||
| - T2.3 依赖 T2.2 | ||
| - T4.1 依赖 T1.1, T2.3, T3.1 | ||
|
|
||
| ## Parallelizable Work | ||
| - T1.1 和 T2.1 可并行开发 |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,7 +24,15 @@ const { | |||||||||
| section = "", | ||||||||||
| } = Astro.props; | ||||||||||
|
|
||||||||||
| const { title, description, featured, ...props } = data; | ||||||||||
| const { title, description, featured, ogImage, ...props } = data; | ||||||||||
|
|
||||||||||
| // 解析 ogImage URL | ||||||||||
| let ogImageUrl: string | undefined; | ||||||||||
| if (typeof ogImage === "string") { | ||||||||||
| ogImageUrl = ogImage; | ||||||||||
| } else if (ogImage?.src) { | ||||||||||
| ogImageUrl = ogImage.src; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // 自动生成摘要:如果 description 为空,从文章内容提取 | ||||||||||
| function getExcerpt(content: string, maxLength: number = 350): string { | ||||||||||
|
|
@@ -76,5 +84,34 @@ const excerpt = description || (body ? getExcerpt(body) : ""); | |||||||||
| } | ||||||||||
| </div> | ||||||||||
| <Datetime {...props} /> | ||||||||||
| {excerpt && <p class="mt-1 line-clamp-3 text-foreground/80">{excerpt}</p>} | ||||||||||
| { | ||||||||||
| excerpt && ( | ||||||||||
| <div | ||||||||||
| class:list={[ | ||||||||||
| "mt-1 flex gap-4", | ||||||||||
| { "flex-col sm:flex-row sm:items-start": ogImageUrl }, | ||||||||||
| ]} | ||||||||||
| > | ||||||||||
| {ogImageUrl && ( | ||||||||||
| <div class="group/thumb shrink-0 overflow-hidden rounded-md border border-foreground/10 shadow-sm"> | ||||||||||
| <img | ||||||||||
| src={ogImageUrl} | ||||||||||
| alt={title} | ||||||||||
| class="h-24 w-full object-cover transition-transform duration-300 group-hover/thumb:scale-105 sm:h-24 sm:w-40" | ||||||||||
| loading="lazy" | ||||||||||
| decoding="async" | ||||||||||
|
||||||||||
| decoding="async" | |
| decoding="async" | |
| width="160" | |
| height="96" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -99,6 +99,19 @@ const nextPost = | |||||
| class:list={["app-layout pb-12", { "mt-8": !SITE.showBackButton }]} | ||||||
| data-pagefind-body | ||||||
| > | ||||||
| { | ||||||
| ogImageUrl && ( | ||||||
| <div class="group/cover mb-6 overflow-hidden rounded-xl border border-foreground/10 shadow-lg shadow-foreground/5 transition-shadow duration-300 hover:shadow-xl hover:shadow-foreground/10"> | ||||||
| <img | ||||||
| src={ogImageUrl} | ||||||
| alt={title} | ||||||
|
||||||
| alt={title} | |
| alt="" |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding width and height attributes to the img tag to prevent layout shift during image loading. This helps with Cumulative Layout Shift (CLS) metrics and improves user experience. Since the image uses object-cover, you can set explicit dimensions that match the container's aspect ratio.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the
altattribute is set to the article title, this may not be the most descriptive alternative text for screen readers. Consider using a more descriptive alt text that describes what the thumbnail image actually depicts, or use an empty alt (alt="") if the image is purely decorative and doesn't add meaningful content beyond what the title already conveys.