1698 字
8 分钟
为博客添加影视墙功能

前言#

最近在整理自己看过的电影和电视剧时,想在博客上添加一个”影视墙”栏目,用来记录和展示这些作品。这样不仅方便自己回顾,也能和朋友分享推荐。

这个功能的整个开发工作由 Claude Code(Anthropic 的 AI 编程助手)完成。我只需要提供需求描述和实施计划,Claude Code 就自动完成了从代码实现到问题排查的全部工作。

本文将记录整个开发过程,包括需求分析、技术选型、实现细节以及遇到的问题和解决方案,同时也展示了 AI 辅助编程的实际效果。

需求分析#

在开始开发之前,我先明确了具体需求:

功能需求#

  • 展示内容:电影和电视剧的基础信息(名称、封面图、评分、描述)
  • 页面类型:仅需列表页,不需要详情页
  • 交互功能:简单展示即可,无需筛选和搜索
  • 内容管理:使用 Markdown 文件管理,方便维护

设计需求#

  • 视觉风格:与博客现有风格保持一致
  • 布局方式:卡片式网格布局
  • 响应式:支持移动端、平板、桌面多种设备
  • 简洁优先:最终决定只显示封面图和电影名称

技术架构#

本博客基于 Fuwari 模板,使用以下技术栈:

  • Astro 5.x:静态站点生成器
  • Tailwind CSS:样式框架
  • Zod:内容集合 schema 验证
  • Svelte:部分交互组件

实现步骤#

1. 定义内容集合 Schema#

首先在 src/content/config.ts 中添加新的内容集合:

const moviesCollection = defineCollection({
schema: z.object({
title: z.string(), // 影视名称
published: z.date(), // 观看/添加日期
draft: z.boolean().optional().default(false), // 是否草稿
description: z.string().optional().default(""), // 简短描述
rating: z.number().min(0).max(10).optional(), // 评分 (0-10)
image: z.string().optional().default(""), // 封面图路径
year: z.number().optional(), // 上映年份
director: z.string().optional(), // 导演
genre: z.array(z.string()).optional().default([]), // 类型标签
}),
});
export const collections = {
posts: postsCollection,
spec: specCollection,
movies: moviesCollection, // 新增
};

设计考虑

  • 大部分字段设为可选,降低使用门槛
  • draft 字段支持草稿功能,生产环境自动隐藏
  • 评分限制在 0-10 范围,确保数据有效性

2. 创建工具函数#

参考现有的 content-utils.ts,创建了 movie-utils.ts

import { type CollectionEntry, getCollection } from "astro:content";
export type MovieForList = {
slug: string;
data: CollectionEntry<"movies">["data"];
};
async function getRawSortedMovies() {
const allMovies = await getCollection("movies", ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});
return allMovies.sort((a, b) => {
const dateA = new Date(a.data.published);
const dateB = new Date(b.data.published);
return dateA > dateB ? -1 : 1;
});
}
export async function getSortedMoviesList(): Promise<MovieForList[]> {
const sortedFullMovies = await getRawSortedMovies();
return sortedFullMovies.map((movie) => ({
slug: movie.slug,
data: movie.data,
}));
}

关键点

  • 按发布日期降序排序(最新的在前)
  • 生产环境自动过滤草稿
  • 只返回必要的数据,减少传递开销

3. 设计卡片组件#

最初设计的 MovieCard.astro 组件包含了丰富的信息:

  • 封面图(2:3 竖版海报比例)
  • 评分徽章(右上角星级图标)
  • 年份标签(左上角)
  • 导演、类型标签
  • 描述文字
  • 添加日期

但经过实际展示和调整,最终简化为只显示:

  • 封面图
  • 电影名称(单行,超出显示省略号)

简化后的代码

<div class:list={["card-base flex flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]} style={style}>
<!-- 封面图区域 -->
{hasCover && (
<div class="relative w-full aspect-[2/3] overflow-hidden rounded-t-xl">
<div class="absolute pointer-events-none z-10 w-full h-full bg-black/0 hover:bg-black/20 transition"></div>
<ImageWrapper
src={image}
basePath={path.join("content/movies/", slug)}
alt={`Cover of ${title}`}
class="w-full h-full"
/>
</div>
)}
<!-- 内容区域 - 只保留标题 -->
<div class="px-3 pb-3 pt-2">
<h3 class="text-base font-bold text-90 text-center truncate">{title}</h3>
</div>
</div>

设计优化

  • truncate 类名确保标题单行显示
  • 调整内边距减少下方空白
  • 字体从 text-lg 改为 text-base 更适合单行

4. 创建列表页面#

src/pages/movies.astro 使用响应式网格布局:

<MainGridLayout title="影视墙">
<div class="flex flex-col gap-8">
<!-- 页面标题和统计 -->
<div class="card-base px-8 py-6">
<h1 class="text-3xl font-bold text-90 mb-2">影视墙</h1>
<p class="text-60">
共收录 {sortedMoviesList.length} 部影视作品
</p>
</div>
<!-- 影视卡片网格 -->
{sortedMoviesList.length > 0 ? (
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6 px-2">
{sortedMoviesList.map((movie) => (
<MovieCard
slug={movie.slug}
title={movie.data.title}
published={movie.data.published}
rating={movie.data.rating}
year={movie.data.year}
director={movie.data.director}
genre={movie.data.genre}
image={movie.data.image}
description={movie.data.description}
style=""
/>
))}
</div>
) : (
<div class="card-base px-8 py-12 text-center">
<p class="text-60 text-lg">暂无影视作品</p>
<p class="text-50 text-sm mt-2">在 src/content/movies/ 目录下添加 Markdown 文件即可</p>
</div>
)}
</div>
</MainGridLayout>

响应式设计

  • 移动端(默认):2 列
  • 平板(md,≥768px):3 列
  • 桌面(lg,≥1024px):4 列
  • 大屏(xl,≥1280px):5 列

5. 添加导航链接#

src/config.ts 中添加导航配置:

export const navBarConfig: NavBarConfig = {
links: [
LinkPreset.Home,
LinkPreset.Archive,
{
name: "影视墙",
url: "/movies/", // 注意末尾的斜杠
},
LinkPreset.About,
// ...
],
};

重要细节:URL 必须以 / 结尾(/movies/),否则会出现 404 错误。

遇到的问题与解决方案#

问题 1:README 文件导致构建失败#

错误信息

[InvalidContentEntryDataError] movies → readme data does not match collection schema.
title**: **title: Required

原因:Astro 会处理 src/content/movies/ 目录下的所有 .md 文件,包括 README.md

解决方案:将 README.md 重命名为 _.README.md(前缀下划线),Astro 会忽略以 _ 开头的文件

问题 2:导航链接 404#

现象:直接访问 /movies/ 正常,但点击导航栏链接显示 404

原因:导航配置中 URL 是 /movies(无末尾斜杠),而 Astro 生成的路由是 /movies/

解决方案:将导航链接改为 /movies/(带末尾斜杠)

问题 3:TypeScript 类型错误#

错误信息

Type 'MovieForList' is missing the following properties from type 'CollectionEntry<"movies">': id, render, body, collection

原因:最初 MovieCard 组件的 entry prop 要求完整的 CollectionEntry 类型,但 movie-utils.ts 返回的是简化类型

解决方案:将 MovieCard 组件改为接收 slug 字符串而不是完整的 entry 对象

内容管理#

创建影视条目非常简单,只需在 src/content/movies/ 目录下创建 Markdown 文件:

---
title: '肖申克的救赎'
published: 2026-02-15
rating: 9.7
year: 1994
director: 'Frank Darabont'
genre: ['剧情', '犯罪']
image: '/images/movies/shawshank.jpg'
description: '一场关于希望与自由的深刻诠释'
---
这里可以写观影笔记(可选)

图片放在 public/images/movies/ 目录下,在 frontmatter 中引用即可。

总结#

通过本次开发,我成功为博客添加了一个简洁优雅的影视墙功能。值得一提的是,整个功能的代码实现完全由 Claude Code 完成,包括:

  • ✅ 6 个文件的创建和修改
  • ✅ 完整的 TypeScript 类型定义
  • ✅ 响应式组件和页面设计
  • ✅ 问题排查和修复
  • ✅ 构建测试验证

整个开发过程非常流畅:

  1. 我提供详细的需求和实施计划
  2. Claude Code 按照计划逐步实现
  3. 实时测试验证,遇到问题立即修复
  4. 从开始到完成约 30 分钟

这次体验展示了 AI 辅助编程的强大能力:

  • 效率高:从需求到实现快速完成
  • 质量好:代码规范,类型安全,无新增错误
  • 可维护:代码结构清晰,文档完善
  • 零门槛:只需描述需求,无需手写代码

参考资源#

为博客添加影视墙功能
https://fuwari.vercel.app/posts/movie-wall-feature/
作者
Croco
发布于
2026-02-15
许可协议
CC BY-NC-SA 4.0