Avatar

CaiCai

INITIALIZING SYSTEM

封面

动漫追番页面技术总结

写作时间:2026-07-02 14:59:44
# 新增页面
👁 次阅读

一、项目架构

XHBlogs/                          ← 博客前端(展示)
  app/anime/page.tsx               → 入口
  app/anime/AnimeClient.tsx        → 展示页组件
  app/api/anime/image/route.ts     → 图片代理
  data/anime-list.json             → 番剧数据(管理端同步过来)

#文件操-作说明1my-blog-manager/app/anime/page.tsx新建追番管理页(400+ 行)2my-blog-manager/app/api/anime/route.ts新建Next.js API 路由3my-blog-manager/app/api/anime/image/route.ts新建图片代理(防盗链)4my-blog-manager/data/anime-list.json新建初始数据(可空数组 [])5my-blog-manager/components/Navbar.tsx修改加一行 { name: '动漫', href: '/anime' }6my-blog-manager/cms_core/api/sync.py修改SYNC_FILES 加 "data/anime-list.json"7XHBlogs/app/anime/page.tsx新建博客展示页入口8XHBlogs/app/anime/AnimeClient.tsx新建博客展示页组件9XHBlogs/app/api/anime/image/route.ts新建博客端图片代理10XHBlogs/components/Navbar.tsx修改加一行 { name: '动漫', href: '/anime' }

共 6 个新文件 + 4 个修改

三、管理端 — 追番管理页面

my-blog-manager/app/anime/page.tsx

页面结构

  1. 标题栏:"追番管理"标题 + 副标题 + "添加番剧"按钮
  2. 统计卡片:总追番 / 平均评分 / TV 动画 / 剧场版(4栏)
  3. 搜索筛选栏:搜索框 + 类型筛选(全部/TV/剧场版)+ 排序下拉
  4. 卡片网格:响应式 2~5 列,每页 10 张,分页控件始终显示
  5. 添加弹窗:粘贴链接 → 获取信息 → 预览编辑 → 添加到列表
  6. 删除弹窗:确认删除

核心功能列表

功能实现📊 统计4 个 StatCard 组件,读取 animeList 计算🔍 搜索标题、标签模糊匹配🏷️ 筛选useState typeFilter: 'all'|'tv'|'movie'📋 排序默认不排序(保持自定义顺序)/ 评分 / 年份📄 分页perPage=10,始终显示分页控件➕ 添加弹窗 → 粘贴链接 → POST /api/anime → 自动抓取 → 预览 → 保存✏️ 预览编辑标题、封面链接、年份、标签(用/分隔)、TV↔剧场版切换🗑️ 删除确认弹窗 → DELETE /api/anime?id=xxx❤️ 收藏封面左上爱心按钮 → PATCH /api/anime?id=xxx🔄 拖拽排序HTML5 Drag & Drop + POST /api/anime {ids:[...]}

关键 State

animeList: Anime[]        — 番剧列表
loading: boolean          — 加载中
search: string            — 搜索词
typeFilter: 'all'|'tv'|'movie'
sortBy: 'default'|'rating-desc'|'rating-asc'|'year-desc'|'year-asc'
page: number              — 当前页
addOpen: boolean          — 添加弹窗
url: string               — 输入框的链接
fetching/saving: boolean  — 加载状态
preview: Anime | null     — 抓取到的预览数据
deleteTarget: Anime | null — 待删除项
dragItem: Anime | null    — 拖拽中的卡片

接口定义

interface Anime {
  id: string; title: string; originalTitle: string; poster: string;
  type: 'tv' | 'movie'; rating: number; year: string; overview: string;
  link: string; source: string; epStatus?: string; tags?: string[];
  favorite?: boolean;
}

来源名称映射: bilibili→哔哩哔哩, tencent→腾讯视频, iqiyi→爱奇艺, youku→优酷, douban→豆瓣

四、管理端 — Next.js API 路由

my-blog-manager/app/api/anime/route.ts

GET    → 读取 data/anime-list.json → { animeList: [...] }

POST   → body.ids    → 重排序(保存拖拽后的新顺序)
       → body.url    → 抓取番剧信息
         ├─ Bilibili: 调 api.bilibili.com/pgc/view/web/season?season_id={ssid}
         └─ 其他平台: 抓取网页 HTML → 解析 og:title/og:image/og:description
       → body.anime  → 添加到列表(去重检查)

PATCH  → ?id=xxx → 切换 favorite 状态

DELETE → ?id=xxx → 从列表删除

Bilibili API 调用

const url = `https://api.bilibili.com/pgc/view/web/season?season_id=${ssid}`;
const res = await fetch(url, {
  headers: { 'User-Agent': 'Mozilla/5.0', 'Referer': 'https://www.bilibili.com/' }
});
// 返回: title, cover, rating, evaluate, season_type_name, new_ep, styles 等

通用 Open Graph 抓取

const html = await fetch(pageUrl).then(r => r.text());
const title = og('title') || html.match(/<title>([^<]+)<\/title>/)?.[1];
const poster = og('image');
const description = og('description');

五、管理端 — 图片代理

my-blog-manager/app/api/anime/image/route.ts

export async function GET(req: Request) {
  const url = new URL(req.url).searchParams.get('url');
  const res = await fetch(url, {
    headers: { 'Referer': 'https://www.bilibili.com/', 'User-Agent': 'Mozilla/5.0' }
  });
  const buffer = await res.arrayBuffer();
  return new Response(buffer, {
    headers: { 'Content-Type': res.headers.get('content-type') || 'image/jpeg',
               'Cache-Control': 'public, max-age=86400' }
  });
}

**为什么需要代理:**Bilibili 图片 CDN 检查 Referer 头。浏览器直接 不传 Referer 会被拒。服务端代理加上 Referer 头即可正常获取。

**判断是否需要代理:**检查 URL 是否包含 hdslb.combilibili,不要只凭 source 字段判断(用户可能手动粘贴 Bilibili 图片 URL 但 source 是 other)。

六、导航栏修改

my-blog-manager/components/Navbar.tsx

const navLinks = [
  ...
  { name: '音乐', href: '/music' },
  { name: '动漫', href: '/anime' },  // ← 加这一行
  { name: '🌳 灵境', href: '/tree' },
  ...
];

XHBlogs/components/Navbar.tsx 同样加一行。

七、同步配置

my-blog-manager/cms_core/api/sync.py

SYNC_FILES = [
  ...
  "data/anime-list.json",  // ← 加这一行
  ...
]

八、博客前端 — 展示页

入口 XHBlogs/app/anime/page.tsx

import AnimeClient from "./AnimeClient";
export const metadata = { title: "追番列表 | " + siteConfig.title };
export default function AnimePage() { return <AnimeClient />; }

组件 XHBlogs/app/anime/AnimeClient.tsx

读取 /api/anime(或直接读 data/anime-list.json),展示统计卡片 + 卡片网格 + 搜索筛选 + 分页。

**图片处理:**Bilibili 封面走 /api/anime/image?url=... 代理

**卡片特性:**骨架占位(onLoad opacity 切换)、评分角标、类型/来源标签、进度、悬停遮罩

来源名映射:{ bilibili:'哔哩哔哩', tencent:'腾讯', iqiyi:'爱奇艺', youku:'优酷', douban:'豆瓣' }

图片代理 XHBlogs/app/api/anime/image/route.ts

同管理端,复制即可。

九、避坑清单

坑解决方案webview 没有地址栏,用户找不到管理页管理页做成独立页面 + Navbar 入口,不要塞到 admin tabwindow_config.json 不存在API 端口配置在 public/backend_config.json依赖 CMS FastAPI 动态端口经常连不上用 Next.js API 直接读写本地 JSON,不依赖 CMS 后端Bilibili 封面图片不显示必须走代理加 Referer 头非 Bilibili 平台没有年份预览区加可编辑年份输入框非 Bilibili 平台封面为空 →



报错封面为空时不渲染 img,显示占位图标手动粘贴的 Bilibili 封面不显示判断是否走代理要看 URL 内容(含 hdslb.com),不是看 source 字段标签不匹配 导致按钮失效仔细检查每一个 HTML 标签闭合拖拽排序后又被评分排序覆盖默认 sortBy 改为 'default'(不排序),保持手动顺序卡片图片可拖拽 → 抢走卡片拖拽事件图片加 draggable={false}收藏卡片排名被评分排序覆盖收藏排序始终在最外层(不管选什么排序)分页不显示 → 新加的番剧看不到perPage=10,分页始终显示。添加后自动跳到最后一页

十、数据流

┌─────────────────────────────────────────────────────┐
│ 管理端 (my-blog-manager)                             │
│                                                      │
│  Navbar "动漫" → /anime                              │
│    ├─ 点"添加番剧" → 弹窗                             │
│    │   ├─ 粘贴链接 → POST /api/anime {url}            │
│    │   │   ├─ Bilibili → API 抓取                     │
│    │   │   └─ 其他平台 → Open Graph 抓取              │
│    │   ├─ 预览编辑(封面/年份/标签/类型)              │
│    │   └─ 添加到列表 → POST /api/anime {anime}        │
│    │       → 写入 data/anime-list.json                │
│    ├─ 爱心收藏 → PATCH /api/anime?id=xxx              │
│    ├─ 拖拽排序 → POST /api/anime {ids:[...]}          │
│    └─ 删除 → DELETE /api/anime?id=xxx                 │
│                                                      │
│  Navbar "同步本地" → /api/sync/execute                │
│    → 复制 data/anime-list.json 到 XHBlogs/data/      │
└─────────────────────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 博客前端 (XHBlogs)                                   │
│                                                      │
│  Navbar "动漫" → /anime                              │
│    → AnimeClient 读取 data/anime-list.json           │
│    → 展示卡片网格 + 搜索 + 筛选 + 分页               │
└─────────────────────────────────────────────────────┘

十一、初始化数据示例

data/anime-list.json 初始内容:

[]

空数组即可。第一条数据由管理端添加。