From 6133d5452a18ce968c46483d50cba35ee2850a2a Mon Sep 17 00:00:00 2001 From: gizeku Date: Tue, 25 Aug 2026 19:04:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8C/=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MovieCard.jsx | 62 +++++++++++++++ README.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++ download.js | 100 ++++++++++++++++++++++++ posters.bat | 1 + visual.mdx | 78 +++++++++++++++++++ 5 files changed, 450 insertions(+) create mode 100644 MovieCard.jsx create mode 100644 README.md create mode 100644 download.js create mode 100644 posters.bat create mode 100644 visual.mdx diff --git a/MovieCard.jsx b/MovieCard.jsx new file mode 100644 index 0000000..0d72275 --- /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/README.md b/README.md new file mode 100644 index 0000000..2d9b63a --- /dev/null +++ b/README.md @@ -0,0 +1,209 @@ +# docusaurus收藏卡片 +[我现在blog的影像收藏页面](https://pennameco.xyz/vault/visual/)的实现方式,应该是可以直接用的。需要的话拿去喂ai修改也行。 + +🔧需要的东西 - 注册OMDb(邮箱申请就行),docusaurus项目。 + +⚠️注意: +* 目前移动端适配有问题,渲染框架会打架,我没精力排查。不过其实影响不大,除非强迫症。 +* MovieCard.jsx可能存在大小写问题,我没精力查。不过大概率没事。 + +## 路径图、组件 +```text +项目根目录/ +├── src/ +│ ├── pages/ +│ │ └── visual.mdx # 电影列表页面 +│ └── components/ +│ └── MovieCard.jsx # 电影卡片组件 +├── scripts/ +│ └── download-posters/ +│ └── download.js # 海报批量下载脚本(API Key 直接写在里面) +├── static/ +│ └── img/ +│ └── posters/ # 海报图片存放目录(脚本自动创建) +└── posters.bat # Windows 批处理运行脚本 +``` +## 需要修改的东西 +### OMDb、download.js +访问[OMDb](http://www.omdbapi.com/apikey.aspx)注册申请一个API Key。 +然后修改 `download.js` +```js +const API_KEY = '替换你的OMDb-key'; +``` +### 给visual.mdx增加封面路径 +添加和修改这句 +```mdx + // 在此添加更多电影,格式:{imdb:'ttxxxxxx', title:'片名'} + {imdb:'tt0095497', title:'基督最后的诱惑'}, //←修改这个部分就行 + // 复制上面,添加更多... +``` + +### 运行`posters.bat`抓取图片 +如果你用的不是windows,那可以直接运行。 +```bash + node scripts/download-posters/download.js +``` +他会把`visual.mdx`写的列表自己下载,看路径图。第一次运行会自动生成文件夹和内容。 + +## ⚠️ 修改和注意 +如果需要改`visual.mdx`的文件名,需要同步修改`download.js`l检索文件。 +download.js +```js +const mdxPath = path.join(__dirname, '../../src/pages/visual.mdx'); //←修改这个(默认是pages/visual.mdx) +``` + +## 版权和协议 +AI吐代码,我调试的。没有版权,没有协议,随便用。出了问题也别找我,不过可以跟[我聊聊看](https://pennameco.xyz/)怎么优化。 + +## 组件我放在项目文件里面了,但是如果你需要直接复制也能用下面的文档。不全,但是我也不打算删了👋。 + +### src/components/moviecard.jsx +```jsx +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)} + /> + + ); +} +``` + + +### src/pages/visual.mdx + + +```mdx +--- +title: 影像收藏 +--- + +import MovieCard from '@site/src/components/MovieCard'; + + + +
+## 标题 +### 电影 + +
+ {[ + // 在此添加更多电影,格式:{imdb:'ttxxxxxx', title:'片名'} + {imdb:'tt0095497', title:'基督最后的诱惑'}, + // 添加更多... + ].map(item => ( +
+
+ +
+

{item.title}

+
+ ))} +
+ +
+``` \ No newline at end of file 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..1db0eae --- /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 => ( +
+
+ +
+

{item.title}

+
+ ))} +
+ +
\ No newline at end of file