书籍元数据展示

obsidian中的md文档元数据展示在quartz中

1. 确保 Frontmatter 格式正确

如:

---
title: 书名
author: 作者
rating: 5
status: 已读
---

2. 配置 Quartz 显示 Frontmatter

  • quartz/components/中,创建一个新文件BookInfo.tsx
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
const BookInfo: QuartzComponent = ({ fileData }: QuartzComponentProps) => {
const fm = fileData.frontmatter
if (!fm) return null
// 严格判断:只显示 tags 包含 "book" 的页面
const isBook = fm.tags && Array.isArray(fm.tags) && fm.tags.includes('book')
// 如果不是书籍页面,不显示任何内容
if (!isBook) return null
const getImageUrl = (url: string): string => {
if (url.startsWith('http://') || url.startsWith('https://')) {
return `https://images.weserv.nl/?url=${encodeURIComponent(url)}`
}
return url
}
return (
<div class="book-meta">
<h3>📚 书籍信息</h3>
<div class="book-main">
{/* 左侧:封面图片 */}
{fm.封面 && typeof fm.封面 === 'string' && (
<div class="book-picture">
<img
src={getImageUrl(fm.封面)}
alt={(fm.title as string) || "书籍封面"}
loading="lazy"
/>
</div>
)}
{/* 右侧:书籍信息 */}
<div class="book-content">
{/* 基本信息 */}
<div class="book-section">
{fm.title && (
<p><strong>书名:</strong>{String(fm.title)}</p>
)}
{fm.originalTitle && (
<p><strong>原标题:</strong>{String(fm.originalTitle)}</p>
)}
{/* 评分信息 */}
{(fm.scoreStar || fm.score) && (
<p className="rating-line">
<strong>豆瓣评分:</strong>
{fm.scoreStar && <span className="stars">{fm.scoreStar}</span>}
&nbsp;&nbsp;
{fm.score && <span className="score">{fm.score}</span>}
</p>
)}
{fm.myRate && (
<p><strong>我的评分:</strong>{String(fm.myRate)}</p>
)}
{fm.author && (
<p><strong>作者:</strong>{String(fm.author)}</p>
)}
{fm.publishDate && (
<p><strong>出版:</strong>{String(fm.publishDate)}</p>
)}
{fm.yearPublished && (
<p><strong>出版年份:</strong>{String(fm.yearPublished)}</p>
)}
{fm.totalPage && (
<p><strong>总页数:</strong>{String(fm.totalPage)}</p>
)}
</div>
{/* 阅读进度 */}
<div class="book-section">
{fm.阅读状态 && Array.isArray(fm.阅读状态) && fm.阅读状态.length > 0 && (
<p><strong>阅读状态:</strong>{fm.阅读状态.join(", ")}</p>
)}
{fm.currentPage && (
<p><strong>当前页码:</strong>{String(fm.currentPage)}</p>
)}
{fm.阅读进度 && (
<p><strong>阅读进度:</strong>{String(fm.阅读进度)}</p>
)}
{fm.添加时间 && (
<p><strong>添加时间:</strong>{String(fm.添加时间)}</p>
)}
{fm.开始阅读 && (
<p><strong>开始阅读:</strong>{String(fm.开始阅读)}</p>
)}
{fm.结束阅读 && (
<p><strong>结束阅读:</strong>{String(fm.结束阅读)}</p>
)}
</div>
</div>
</div>
{/* 简介 - 单独一行,可展开 */}
{fm.desc && (
<details class="book-description">
<summary><strong>简介:</strong></summary>
<div class="desc-content">{String(fm.desc)}</div>
</details>
)}
</div>
)}
BookInfo.displayName = "BookInfo"
export default (() => BookInfo) satisfies QuartzComponentConstructor
  • 导出组件 在 quartz/components/index.ts
import ArticleTitle from "./ArticleTitle"
import Content from "./Content"
import TagList from "./TagList"
// ... 其他导入
import BookInfo from "./BookInfo"  // 添加这一行
export default {
  ArticleTitle,
  Content,
  TagList,
  // ... 其他组件
  BookInfo,  // 添加这一行
}
  • 注册组件 在quartz.layout.ts中,添加新建的组件
export const defaultContentPageLayout: PageLayout = {
  beforeBody: [
    Component.Breadcrumbs(),
    Component.ArticleTitle(),
    Component.BookInfo(),  // 添加这一行
    Component.ContentMeta(),
    Component.TagList(),
  ],
  // ...
}
  • 添加样式 在`quartz/components/styles 中分别新建bookInfo.scss和movieInfo.scss
.book-meta {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 12px;
padding: 15px;
// margin: 30px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
h3 {
margin: 0 0 15px 0;
color: #2c3e50;
font-size: 1.5em;
border-bottom: 2px solid #3498db;
padding-bottom: 5px;
}
// 主体布局:图片 + 信息
.book-main {
display: flex;
gap: 20px;
margin-bottom: 15px;
align-items: flex-start; // 顶部对齐
}
// 左侧封面
.book-picture {
flex-shrink: 1.2; // 不缩小
width: 120px;
img {
width: 120px;
height: 170px;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
display: block;
}}
// 右侧内容区
.book-content {
flex: 1;
min-width: 0; // 允许收缩
display: flex;
flex-direction: column;
// gap: 15px;
}
// 每个信息区块
.book-section {
p {
margin: 0;
color: #34495e;
line-height: 1.6;
font-size: 0.95em;
strong {
color: #2c3e50;
margin-right: 8px;
min-width: 80px;
display: inline-block;
}
}
}
// 简介 - 使用 details/summary 实现展开收起
.book-description {
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #bdc3c7;
summary {
cursor: pointer;
user-select: none;
// padding: 8px 0;
color: #2c3e50;
font-weight: bold;
// list-style-position: outside;
&:hover {
color: #3498db;
}
&::marker {
content: '▶ ';
}
}
&[open] summary::marker {
content: '▼ ';
}
.desc-content {
// background: white;
// background: var(--lightgray);
background: #fffdf6;
padding: 15px;
border-radius: 6px;
line-height: 1.8;
color: #555;
font-size: 0.9em;
margin-top: 10px;
max-height: 300px; // 限制最大高度
overflow-y: auto; // 超出显示滚动条
// 美化滚动条
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
&:hover {
background: #555;
}
}
}
}
// 响应式:小屏幕下改为上下布局
@media (max-width: 768px) {
.book-main {
flex-direction: column;
align-items: center;
}
.book-cover {
width: 150px;
img {
width: 150px;
height: 210px;
}
}
.book-content {
width: 100%;
}
}
}
  • 引用样式 在quartz/styles/custom.scss 中引用
@use "../components/styles/bookInfo.scss";
@use "../components/styles/movieInfo.scss";