上传文件至「/」
This commit is contained in:
@@ -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 (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: CARD_HEIGHT,
|
||||||
|
background: '#f8f8f8',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: '#bbb',
|
||||||
|
fontSize: '12px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
无海报
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`https://www.imdb.com/title/${imdbId}/`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: CARD_HEIGHT,
|
||||||
|
textDecoration: 'none',
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={posterUrl}
|
||||||
|
alt="电影海报"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'scale-down',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
onError={() => setHasError(true)}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: CARD_HEIGHT,
|
||||||
|
background: '#f8f8f8',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: '#bbb',
|
||||||
|
fontSize: '12px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
无海报
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`https://www.imdb.com/title/${imdbId}/`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: CARD_HEIGHT,
|
||||||
|
textDecoration: 'none',
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={posterUrl}
|
||||||
|
alt="电影海报"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'scale-down',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
onError={() => setHasError(true)}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### src/pages/visual.mdx
|
||||||
|
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
---
|
||||||
|
title: 影像收藏
|
||||||
|
---
|
||||||
|
|
||||||
|
import MovieCard from '@site/src/components/MovieCard';
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
.container,.main-wrapper,main>div,.theme-doc-markdown{max-width:100%!important;padding:0!important;margin:0!important}
|
||||||
|
body,html{overflow-x:hidden;width:100%}
|
||||||
|
.movie-grid>div{padding:4px!important}
|
||||||
|
.movie-grid .movie-card-wrapper,.movie-grid .movie-card-wrapper>*{padding:0!important;margin:0!important;line-height:0!important;font-size:0!important;display:block!important}
|
||||||
|
.movie-grid .movie-card-wrapper img,.movie-grid .movie-card-wrapper picture,.movie-grid .movie-card-wrapper figure{display:block!important;margin:0!important;padding:0!important}
|
||||||
|
.movie-grid .movie-title{font-size:16px;font-weight:600;color:var(--ifm-font-color-base);line-height:1.3;margin:4px 0 0!important;padding:0!important}
|
||||||
|
[data-theme=dark] .movie-grid>div{border-color:rgba(255,255,255,0.15)!important;background:rgba(255,255,255,0.03)!important}
|
||||||
|
|
||||||
|
/* 桌面端:5列 */
|
||||||
|
@media(min-width:1201px){.movie-grid{grid-template-columns:repeat(5,1fr)!important;gap:16px!important}}
|
||||||
|
|
||||||
|
/* 平板:4列 */
|
||||||
|
@media(max-width:1200px){.movie-grid{grid-template-columns:repeat(4,1fr)!important;gap:14px!important}}
|
||||||
|
|
||||||
|
/* 小平板:3列 */
|
||||||
|
@media(max-width:768px){.movie-grid{grid-template-columns:repeat(3,1fr)!important;gap:12px!important}}
|
||||||
|
|
||||||
|
/* 手机:2列 */
|
||||||
|
@media(max-width:480px){.movie-grid{grid-template-columns:repeat(2,1fr)!important;gap:10px!important}}
|
||||||
|
|
||||||
|
/* 小屏手机:1列 */
|
||||||
|
@media(max-width:360px){.movie-grid{grid-template-columns:repeat(1,1fr)!important;gap:10px!important}}
|
||||||
|
|
||||||
|
/* ===== 移动端修复:统一左对齐 ===== */
|
||||||
|
@media(max-width:768px){
|
||||||
|
div[style*="paddingLeft:200px"] {
|
||||||
|
padding-left:16px!important;
|
||||||
|
padding-right:16px!important;
|
||||||
|
}
|
||||||
|
.movie-grid {
|
||||||
|
max-width:100%!important;
|
||||||
|
width:100%!important;
|
||||||
|
margin-left:0!important;
|
||||||
|
margin-right:0!important;
|
||||||
|
}
|
||||||
|
.movie-grid .movie-card-wrapper img {
|
||||||
|
max-width:100%!important;
|
||||||
|
height:auto!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:480px){
|
||||||
|
div[style*="paddingLeft:200px"] {
|
||||||
|
padding-left:12px!important;
|
||||||
|
padding-right:12px!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {margin-top: 40px !important;}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<div style={{paddingLeft:'200px',paddingRight:'40px',width:'100%',boxSizing:'border-box'}}>
|
||||||
|
## 标题
|
||||||
|
### 电影
|
||||||
|
|
||||||
|
<div className="movie-grid" style={{display:'grid',gridTemplateColumns:'repeat(5,1fr)',gap:'16px',width:'100%',maxWidth:'900px'}}>
|
||||||
|
{[
|
||||||
|
// 在此添加更多电影,格式:{imdb:'ttxxxxxx', title:'片名'}
|
||||||
|
{imdb:'tt0095497', title:'基督最后的诱惑'},
|
||||||
|
// 添加更多...
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.imdb} style={{border:'1px solid rgba(128,128,128,0.25)',background:'rgba(128,128,128,0.02)',padding:'4px',textAlign:'center'}}>
|
||||||
|
<div className="movie-card-wrapper" style={{lineHeight:0,fontSize:0,padding:0,margin:0}}>
|
||||||
|
<MovieCard imdbId={item.imdb} />
|
||||||
|
</div>
|
||||||
|
<p className="movie-title">{item.title}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
```
|
||||||
+100
@@ -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();
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
node scripts/download-posters/download.js
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
title: 影像收藏
|
||||||
|
---
|
||||||
|
|
||||||
|
import MovieCard from '@site/src/components/MovieCard';
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
.container,.main-wrapper,main>div,.theme-doc-markdown{max-width:100%!important;padding:0!important;margin:0!important}
|
||||||
|
body,html{overflow-x:hidden;width:100%}
|
||||||
|
.movie-grid>div{padding:4px!important}
|
||||||
|
.movie-grid .movie-card-wrapper,.movie-grid .movie-card-wrapper>*{padding:0!important;margin:0!important;line-height:0!important;font-size:0!important;display:block!important}
|
||||||
|
.movie-grid .movie-card-wrapper img,.movie-grid .movie-card-wrapper picture,.movie-grid .movie-card-wrapper figure{display:block!important;margin:0!important;padding:0!important}
|
||||||
|
.movie-grid .movie-title{font-size:16px;font-weight:600;color:var(--ifm-font-color-base);line-height:1.3;margin:4px 0 0!important;padding:0!important}
|
||||||
|
[data-theme=dark] .movie-grid>div{border-color:rgba(255,255,255,0.15)!important;background:rgba(255,255,255,0.03)!important}
|
||||||
|
|
||||||
|
/* 桌面端:5列 */
|
||||||
|
@media(min-width:1201px){.movie-grid{grid-template-columns:repeat(5,1fr)!important;gap:16px!important}}
|
||||||
|
|
||||||
|
/* 平板:4列 */
|
||||||
|
@media(max-width:1200px){.movie-grid{grid-template-columns:repeat(4,1fr)!important;gap:14px!important}}
|
||||||
|
|
||||||
|
/* 小平板:3列 */
|
||||||
|
@media(max-width:768px){.movie-grid{grid-template-columns:repeat(3,1fr)!important;gap:12px!important}}
|
||||||
|
|
||||||
|
/* 手机:2列 */
|
||||||
|
@media(max-width:480px){.movie-grid{grid-template-columns:repeat(2,1fr)!important;gap:10px!important}}
|
||||||
|
|
||||||
|
/* 小屏手机:1列 */
|
||||||
|
@media(max-width:360px){.movie-grid{grid-template-columns:repeat(1,1fr)!important;gap:10px!important}}
|
||||||
|
|
||||||
|
/* ===== 移动端修复:统一左对齐 ===== */
|
||||||
|
@media(max-width:768px){
|
||||||
|
div[style*="paddingLeft:200px"] {
|
||||||
|
padding-left:16px!important;
|
||||||
|
padding-right:16px!important;
|
||||||
|
}
|
||||||
|
.movie-grid {
|
||||||
|
max-width:100%!important;
|
||||||
|
width:100%!important;
|
||||||
|
margin-left:0!important;
|
||||||
|
margin-right:0!important;
|
||||||
|
}
|
||||||
|
.movie-grid .movie-card-wrapper img {
|
||||||
|
max-width:100%!important;
|
||||||
|
height:auto!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:480px){
|
||||||
|
div[style*="paddingLeft:200px"] {
|
||||||
|
padding-left:12px!important;
|
||||||
|
padding-right:12px!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {margin-top: 40px !important;}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<div style={{paddingLeft:'200px',paddingRight:'40px',width:'100%',boxSizing:'border-box'}}>
|
||||||
|
## 标题
|
||||||
|
### 电影
|
||||||
|
|
||||||
|
<div className="movie-grid" style={{display:'grid',gridTemplateColumns:'repeat(5,1fr)',gap:'16px',width:'100%',maxWidth:'900px'}}>
|
||||||
|
{[
|
||||||
|
// 在此添加更多电影,格式:{imdb:'ttxxxxxx', title:'片名'}
|
||||||
|
{imdb:'tt0095497', title:'基督最后的诱惑'},
|
||||||
|
// 添加更多...
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.imdb} style={{border:'1px solid rgba(128,128,128,0.25)',background:'rgba(128,128,128,0.02)',padding:'4px',textAlign:'center'}}>
|
||||||
|
<div className="movie-card-wrapper" style={{lineHeight:0,fontSize:0,padding:0,margin:0}}>
|
||||||
|
<MovieCard imdbId={item.imdb} />
|
||||||
|
</div>
|
||||||
|
<p className="movie-title">{item.title}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user