博客折腾记:手搓一个Astro NeoDB 书影音卡片
之前看到不少博客里添加了书影音的小卡片,心痒痒的,毕竟光秃秃的超链接视觉上确实索然无味,于是想在博客里折腾个 NeoDB 书影音卡片(想挺久了),由于咱也不懂代码,就和Gemini慢慢边聊边改(其实还是不习惯用CLI)。我主要负责“提视觉和交互 Bug”,Gemini 负责“疯狂改代码”,提要求这块我可是一点不含糊。几轮死磕下来,也算是能看、能用了。
下面记录一下我在这场折腾中发现并解决的几个问题:
🎨
- 把突兀的“大白补丁”变成微醺底色 刚开始卡片加载出来是一大块纯白色,盖在上面像狗皮膏药一样刺眼。Gemini 改成了“智能混色底”——自动融合了背景色和 4% 的文字主色。既挡住了杂乱的网格线,又融入了主题。
- 解放文本选择:卡片不再是“一碰就跳转”的雷区
最开始 Gemini 把整张卡片做成了超链接。结果我想复制标题时,鼠标一戳直接网页跳转了,体验极其反人类。现在文字可以自由复制,只有点击图片、标题和右下角的
via neodb.social才会跳转。 - 拯救不挂代理手机端的“碎裂海报” 电脑打包时挂了代理,图片显示正常;一开始用手机测试时,发现海报打不开(因为国内直连不上 NeoDB)。后来在代码里套了一层全球通畅的免费图片缓存代理(wsrv.nl),现在手机端不挂梯也能快速出图了。
- UI设计 最开始的卡片太骨感,线条很硬,而且图片外面带个刻意的“拍立得相框”,很土。另外,框体太明显,海报还莫名其妙地偏下。后来剥离了多余的外框,给整体加上了柔和圆润的圆角,收紧了上下间距,并让海报乖乖呆在左上角。简单排版了下,在手机端也会保持优雅的左右并排,不再割裂和臃肿了。
- 简介文字的易读性微调 最后一版测试时,发现简介的字色太浅、太模糊,看起来很费眼。加深了简介的字色透明度,这下终于看清了。
💻 最终成型的组件全量代码
这里放出 Gemini 最终帮我写好的、完美通过编译的 NeoCard.astro 代码。它还支持传入 subtitle 参数,用来手动写上导演、演员或作者年份,填补排版空白:
---
export interface Props {
url: string;
myRating?: string;
myComment?: string;
subtitle?: string;
}
const { url, myRating, myComment, subtitle } = Astro.props;
let data = null;
if (url) {
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, Gecko) Chrome/120.0.0.0'
}
});
if (response.ok) {
const html = await response.text();
const getMeta = (metaName: string) => {
const rgx = new RegExp(`<meta[^>]*property=["']og:${metaName}["'][^>]*content=["']([^"']*)["']`, 'i');
const match = html.match(rgx);
if (match) return match[1];
const rgxSwapped = new RegExp(`<meta[^>]*content=["']([^"']*)["'][^>]*property=["']og:${metaName}["']`, 'i');
const matchSwapped = html.match(rgxSwapped);
return matchSwapped ? matchSwapped[1] : null;
};
const title = getMeta('title');
const image = getMeta('image');
const description = getMeta('description');
if (title) {
const proxiedImage = image ? `https://wsrv.nl/?url=${encodeURIComponent(image)}` : null;
data = {
title: title.replace(' - NeoDB', '').trim(),
image: proxiedImage,
description: description ? description.replace(/"/g, '"').replace(/&/g, '&').trim() : '暂无简介...'
};
}
}
} catch (error) {
console.error(`[NeoCard] 抓取失败: ${url}`);
}
}
---
{data ? (
<div class="neocard-container">
{/* 左侧:海报区 */}
{data.image && (
<a href={url} target="_blank" rel="noreferrer" class="neocard-poster-link">
<img src={data.image} alt={data.title} class="neocard-raw-poster" loading="lazy" />
</a>
)}
{/* 右侧:排版内容区 */}
<div class="neocard-text-layout">
<div class="neocard-heading-group">
<h4 class="neocard-title">
<a href={url} target="_blank" rel="noreferrer" class="neocard-title-link">
{data.title}
</a>
</h4>
{subtitle && <div class="neocard-subtitle">{subtitle}</div>}
</div>
{/* 个人评价便签盒 */}
{(myRating || myComment) && (
<div class="neocard-review-sticker">
{myRating && <div class="neocard-rating-row">RATING: <span class="stars">{myRating}</span></div>}
{myComment && <p class="neocard-comment-row">“{myComment}”</p>}
</div>
)}
{/* 官方简介 */}
<p class="neocard-excerpt">{data.description}</p>
{/* 页脚定位 */}
<div class="neocard-footer-line">
<a href={url} target="_blank" rel="noreferrer" class="neocard-footer-link">
via neodb.social <span class="neocard-arrow">➔</span>
</a>
</div>
</div>
</div>
) : (
<a href={url} target="_blank" class="fallback-link">🔗 查看书影音详情: {url}</a>
)}
<style>
.neocard-container {
display: flex;
flex-direction: row;
border: 1px solid var(--uno-colors-shadow, rgba(0, 0, 0, 0.12));
border-radius: 12px;
overflow: hidden;
isolation: isolate;
margin: 2rem 0;
padding: 1rem 1.2rem;
gap: 1.2rem;
background-color: color-mix(in srgb, var(--uno-colors-background, #fff) 96%, var(--uno-colors-primary, #000) 4%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.03);
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.neocard-container:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
}
.neocard-poster-link {
display: block;
flex-shrink: 0;
align-self: flex-start;
}
.neocard-raw-poster {
width: 95px;
height: auto;
object-fit: contain;
border-radius: 6px;
display: block;
}
.neocard-text-layout {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.neocard-heading-group {
margin-bottom: 0.05rem;
}
.neocard-title {
font-family: var(--uno-fonts-ui), monospace !important;
font-weight: 700 !important;
font-size: 1.25rem !important;
margin: 0 !important;
line-height: 1.2;
}
.neocard-title-link {
color: var(--uno-colors-primary, #000) !important;
text-decoration: none !important;
border-bottom: 1px solid transparent;
transition: border-color 0.2s ease;
}
.neocard-title-link:hover {
border-bottom-color: var(--uno-colors-primary, #000);
background-color: transparent !important;
color: var(--uno-colors-primary, #000) !important;
}
.neocard-subtitle {
font-family: var(--uno-fonts-ui), monospace;
font-size: 0.78rem;
opacity: 0.45;
margin-top: 0.25rem;
font-weight: 600;
}
.neocard-review-sticker {
padding: 0.5rem 0.7rem;
background-color: rgba(0, 0, 0, 0.025);
border-radius: 8px;
}
.neocard-rating-row {
font-family: monospace;
font-size: 0.7rem;
font-weight: 700;
margin-bottom: 0.1rem;
opacity: 0.5;
}
.neocard-rating-row .stars {
color: #fbbf24;
font-size: 0.8rem;
}
.neocard-comment-row {
font-size: 0.85rem !important;
line-height: 1.45;
font-weight: 600;
margin: 0 !important;
}
.neocard-excerpt {
font-size: 0.78rem !important;
line-height: 1.4;
opacity: 0.52;
margin: 0 !important;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.neocard-footer-line {
display: flex;
justify-content: flex-end;
margin-top: 0.1rem;
}
.neocard-footer-link {
font-size: 0.65rem;
opacity: 0.5;
font-family: monospace;
font-weight: bold;
text-decoration: none !important;
color: inherit !important;
transition: opacity 0.2s ease;
}
.neocard-footer-link:hover {
opacity: 0.85;
background-color: transparent !important;
}
.neocard-arrow {
display: inline-block;
transition: transform 0.2s ease;
}
.neocard-container:hover .neocard-arrow {
transform: translateX(2px);
}
@media (max-width: 540px) {
.neocard-container {
flex-direction: row;
gap: 0.9rem;
padding: 0.8rem;
}
.neocard-raw-poster {
width: 68px;
}
.neocard-title {
font-size: 1.15rem !important;
}
}
</style>
📝 怎么日常调用它? 在 MDX 文件里,现在只需要简单地写下这几行代码,就可以输出一个好看好用的排版卡片了:
先加上import NeoCard from ’../../components/NeoCard.astro’;
代码段
<NeoCard
url="https://neodb.social/..."
subtitle="电影 · 历史 ..."
myRating="★★★★★☆☆"
myComment="....。"
/>
效果图如下:
PC、移动端效果
搞定!折腾完毕,暂时能用就行。