diff --git a/MovieCard.jsx b/MovieCard.jsx
new file mode 100644
index 0000000..985e0d2
--- /dev/null
+++ b/MovieCard.jsx
@@ -0,0 +1,62 @@
+import React from 'react';
+
+export default function MovieCard({ imdbId }) {
+ const CARD_WIDTH = 190;
+ const CARD_HEIGHT = 330;
+
+ // 直接生成本地图片路径
+ const posterUrl = `/img/posters/${imdbId}.jpg`;
+
+ // 简单判断图片是否存在(通过 onError 回调处理)
+ const [hasError, setHasError] = React.useState(false);
+
+ if (hasError) {
+ return (
+
+ 无海报
+
+ );
+ }
+
+ return (
+
+
setHasError(true)}
+ />
+
+ );
+}
diff --git a/download.js b/download.js
new file mode 100644
index 0000000..f851859
--- /dev/null
+++ b/download.js
@@ -0,0 +1,100 @@
+// scripts/download-posters/download.js
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+// 访问http://www.omdbapi.com/apikey.aspx申请
+const API_KEY = '你的OMDb-key';
+
+// ⬇️ 自动从 MDX 文件提取电影列表
+function extractMoviesFromMDX(mdxPath) {
+ const content = fs.readFileSync(mdxPath, 'utf8');
+
+ // 匹配 {imdb:'ttxxxxxx', title:'xxx'} 格式
+ const regex = /{imdb:\s*['"](tt\d+|)['"],\s*title:\s*['"]([^'"]+)['"]\s*}/g;
+ const movies = [];
+ let match;
+
+ while ((match = regex.exec(content)) !== null) {
+ const imdb = match[1];
+ const title = match[2];
+ if (imdb) { // 只保留有 imdb id 的条目
+ movies.push({ imdb, title });
+ }
+ }
+
+ return movies;
+}
+
+// 你的 MDX 文件路径(默认是pages/visual.mdx)
+const mdxPath = path.join(__dirname, '../../src/pages/visual.mdx');
+const movies = extractMoviesFromMDX(mdxPath);
+
+console.log(`📚 从 MDX 读取到 ${movies.length} 部电影`);
+
+const posterDir = path.join(__dirname, '../../static/img/posters');
+if (!fs.existsSync(posterDir)) {
+ fs.mkdirSync(posterDir, { recursive: true });
+ console.log('📁 创建目录:', posterDir);
+}
+
+function downloadImage(url, filepath) {
+ return new Promise((resolve, reject) => {
+ const client = url.startsWith('https') ? https : require('http');
+ client.get(url, (response) => {
+ if (response.statusCode === 301 || response.statusCode === 302) {
+ downloadImage(response.headers.location, filepath).then(resolve).catch(reject);
+ return;
+ }
+ if (response.statusCode !== 200) {
+ reject(new Error(`HTTP ${response.statusCode}`));
+ return;
+ }
+ const ws = fs.createWriteStream(filepath);
+ response.pipe(ws);
+ ws.on('finish', () => { ws.close(); resolve(); });
+ ws.on('error', reject);
+ }).on('error', reject);
+ });
+}
+
+async function downloadAllPosters() {
+ console.log('🚀 开始下载海报...');
+ let downloaded = 0;
+ let skipped = 0;
+
+ for (const movie of movies) {
+ if (!movie.imdb) {
+ console.log(`⏭️ ${movie.title} 无IMDb ID,跳过`);
+ continue;
+ }
+
+ const filepath = path.join(posterDir, `${movie.imdb}.jpg`);
+ if (fs.existsSync(filepath)) {
+ console.log(`⏭️ ${movie.title} 图片已存在,跳过`);
+ skipped++;
+ continue;
+ }
+
+ try {
+ console.log(`📡 获取 ${movie.title} (${movie.imdb})...`);
+ const res = await fetch(`https://www.omdbapi.com/?apikey=${API_KEY}&i=${movie.imdb}`);
+ const data = await res.json();
+
+ if (data.Poster === 'N/A' || !data.Poster) {
+ console.log(`❌ ${movie.title} 无海报`);
+ continue;
+ }
+
+ await downloadImage(data.Poster, filepath);
+ console.log(`✅ ${movie.title} (${movie.imdb})`);
+ downloaded++;
+ await new Promise(r => setTimeout(r, 300));
+ } catch (err) {
+ console.log(`❌ ${movie.title} 下载失败: ${err.message}`);
+ }
+ }
+ console.log(`🎉 完成!新增 ${downloaded} 张,跳过 ${skipped} 张`);
+}
+
+downloadAllPosters();
\ No newline at end of file
diff --git a/posters.bat b/posters.bat
new file mode 100644
index 0000000..323960c
--- /dev/null
+++ b/posters.bat
@@ -0,0 +1 @@
+node scripts/download-posters/download.js
\ No newline at end of file
diff --git a/visual.mdx b/visual.mdx
new file mode 100644
index 0000000..4e9fa6a
--- /dev/null
+++ b/visual.mdx
@@ -0,0 +1,78 @@
+---
+title: 影像收藏
+---
+
+import MovieCard from '@site/src/components/MovieCard';
+
+
+
+
+## 标题
+### 电影
+
+
+ {[
+ // 在此添加更多电影,格式:{imdb:'ttxxxxxx', title:'片名'}
+ {imdb:'tt0095497', title:'基督最后的诱惑'},
+ // 添加更多...
+ ].map(item => (
+
+ ))}
+
+
+
\ No newline at end of file