我之前一直使用Notion管理的我的读书笔记,比如书摘之类的,参考我之前写的文章:用「记忆闪卡 Dashboard」高效学习:费曼技巧 × 间隔复习。样式是这样的:


Notion的数据库卡片里的公式属性,不能真正设置“右对齐”。这个真的折磨死强迫症。

后来习惯性的使用Obsidian后,就寻思着怎么把读书管理也迁移到Obsidian中。但是我们都知道,Obsidian的数据库(base)和Notion是有本质区别的:

Notion 的卡片是“组件”,它是一个设计系统而不是数据列表,Obsidian 的卡片是“文件列表“,主要用于浏览文件,管理属性和过滤排序的。

所以Obsidan如果需要实现Notion得效果,我们就需要自己使用DataviewJS和css来辅助。

对比维度 Notion Obsidian Bases
本质 UI驱动数据库 文件驱动索引
卡片渲染 内建组件 只是文件视图
布局控制 内置(Gallery/Card) 基本没有
对齐/排版 UI级控制 不支持
自定义卡片 有限 需要代码(DataviewJS)

1. 插件介绍

整个系统使用8个插件,其中Dataview,Templater以及Milti Properties这三个插件是必须的。Dataview提供了数据展示层,会在书页面自动展示所有卡片;Milti Properties提供类似Notion的多属性支持;Templater则是定义卡片和书籍的模版,便于我们格式化书摘。

2. 整体架构

整体构架继续保持之前Notion的架构:

1
2
3
4
5
6
7
8
9
Book(书)

Flashcard(知识原子)

Dimension(能力维度)

Topic(主题)

Insight(个人认知)

看看最终实现的效果图:

3. 具体步骤

下面就分步介绍下设计过程。

3.1. 模版设计

Templater中有2个模版,分别是Book和Flashcard。其中Book主要用来生成当前所读的书籍以及瀑布流卡片。

瀑布流卡片是通过dataviewjs来生成,也就是我们前面提到的Dataview插件。通过属性book_key与Flashcard关联起来。正文读书卡片中使用dataviewjs实现卡片瀑布流效果。具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
const current = dv.current();
const currentBookKey = current?.book_key;

if (!currentBookKey) {
dv.paragraph("当前页面没有读取到 book_key,请先在 Book 页面 frontmatter 中设置 book_key。");
} else {
const pages = dv.pages('"读书卡片"')
.where(p => p.quote && p.book_key && p.book_key === currentBookKey)
.array();

// Fisher-Yates 洗牌:每次渲染随机卡片顺序
for (let i = pages.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[pages[i], pages[j]] = [pages[j], pages[i]];
}

if (pages.length === 0) {
dv.paragraph(`当前 book_key = "${currentBookKey}" 下还没有匹配的读书卡片。`);
} else {
const container = dv.el("div", "", { cls: "x-card-grid" });

for (const p of pages) {
const moodClass = p.mood ? `x-mood-${String(p.mood).trim()}` : "";
const card = container.createEl("article", {
cls: `x-card x-card-clickable ${moodClass}`
});

// 点击整张卡片跳转
card.addEventListener("click", () => {
app.workspace.openLinkText(p.file.path, "", false);
});

const head = card.createEl("div", { cls: "x-card-head" });

if (p.tags) {
head.createEl("div", {
cls: "x-tag",
text: `# ${p.tags}`
});
}

const body = card.createEl("div", { cls: "x-card-body" });

const lines = String(p.quote)
.replace(/。/g, "。\n")
.replace(/;/g, ";\n")
.replace(/,/g, ",\n");

body.createEl("div", {
cls: "x-quote",
text: lines
});

const foot = card.createEl("div", { cls: "x-card-foot" });

let sourceText = "";
if (p.book?.path) {
sourceText = `——节选自${p.book.path.split("/").pop().replace(/\.md$/, "")}`;
} else if (p.book) {
sourceText = `——节选自${String(p.book)}`.replace(/\[\[|\]\]/g, "");
}

if (sourceText) {
foot.createEl("div", {
cls: "x-source",
text: sourceText
});
}

if (p.chapter || p.page) {
let chapterText = "";
let pageText = "";

if (p.chapter) {
chapterText = `第${p.chapter}章`;
}

if (p.page) {
pageText = `第${p.page}页`;
}

const metaText = [chapterText, pageText]
.filter(Boolean)
.join(",");

foot.createEl("div", {
cls: "x-meta",
text: metaText
});
}

if (p.insight) {
foot.createEl("div", {
cls: "x-insight",
text: `✦ ${p.insight}`
});
}
}
}
}

Flashcard中的book_key与Book模版中的book_key关联。模版如下:

3.2. CSS设计

为了让卡片美观,我们需要自己定义CSS属性。我当然希望卡片在Notion的基础上有所增强,所以我考虑了:

  1. 我希望每张卡片根据表达的意思有不同的颜色展示
  2. 我希望卡片可以随机展示出来
  3. 我希望卡片可以自适应页面宽度
  4. 我希望点击卡片可以支持跳转到具体的Flashcard文章中。

于是,我这样实现了CSsS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/* =========================
内容创作版读书卡片 - 瀑布流宽版最终版
========================= */

/* 页面背景 + 整体宽度 */
.markdown-preview-view {
background: #f3eee6;
max-width: 1650px;
margin: 0 auto;
padding-left: 20px;
padding-right: 20px;
}

/* ===== 卡片瀑布流容器 ===== */
.x-card-grid {
column-count: 3;
column-gap: 20px;
padding: 10px 4px 24px 4px;
}

/* ===== 单卡片 ===== */
.x-card {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
margin: 0 0 20px;
vertical-align: top;

background: #fffaf2;
border: 1.5px solid rgba(165, 138, 110, 0.22);
border-radius: 20px;
padding: 16px 16px 14px 16px;

box-shadow: 0 8px 20px rgba(92, 68, 42, 0.08);
transition:
transform 0.18s ease,
box-shadow 0.18s ease,
border-color 0.18s ease;

break-inside: avoid;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
}

/* 顶部轻微高光 */
.x-card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 60px;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.48) 0%,
rgba(255, 255, 255, 0) 100%
);
pointer-events: none;
}

/* 顶部识别条 */
.x-card::after {
content: "";
position: absolute;
top: 12px;
left: 16px;
width: 40px;
height: 5px;
border-radius: 999px;
background: linear-gradient(90deg, #8eb2f3 0%, #bfd4ff 100%);
opacity: 0.95;
}

.x-card:hover {
transform: translateY(-2px);
box-shadow: 0 12px 24px rgba(92, 68, 42, 0.1);
}

/* 可点击卡片 */
.x-card-clickable {
cursor: pointer;
}

/* ===== 头部 ===== */
.x-card-head {
position: relative;
z-index: 1;
padding-top: 6px;
}

.x-tag {
display: inline-block;
margin-bottom: 8px;
padding: 4px 9px;
border-radius: 999px;
background: rgba(126, 162, 235, 0.18);
color: #5f84d6;
font-size: 10.5px;
line-height: 1.2;
font-weight: 800;
letter-spacing: 0.02em;
}

/* ===== 正文 ===== */
.x-card-body {
position: relative;
z-index: 1;
margin-top: 4px;
}

.x-quote {
white-space: pre-line;
word-break: break-word;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
letter-spacing: 0.01em;
color: #2f2925;
}

/* ===== 底部 ===== */
.x-card-foot {
position: relative;
z-index: 1;
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 7px;
}

/* 来源:在章节页码上面 */
.x-source {
text-align: right;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
color: #8e7f72;
letter-spacing: 0.01em;
}

/* 章节页码:灰色 + 斜体 + 非粗体 */
.x-meta {
font-size: 11.5px !important;
line-height: 1.4 !important;
font-weight: 500 !important;
color: #8b8f98 !important;
font-style: italic !important;
}

/* 个人感悟:单独颜色 + 更明显分割线 */
.x-insight {
font-size: 12.5px !important;
line-height: 1.55 !important;
font-weight: 700 !important;
color: #d65a4a !important;
padding-top: 10px !important;
margin-top: 4px !important;
border-top: 2px dashed rgba(214, 90, 74, 0.7) !important;
}

/* =========================
mood:卡片本身直接有区分色
========================= */

/* 冷静:浅蓝卡 */
.x-mood-冷静 {
background: linear-gradient(180deg, #f7fbff 0%, #eef5ff 100%);
border-color: rgba(122, 167, 255, 0.32);
}
.x-mood-冷静::after {
background: linear-gradient(90deg, #7aa7ff, #c3d7ff);
}
.x-mood-冷静 .x-tag {
background: rgba(122, 167, 255, 0.16);
color: #5b87df;
}

/* 警醒:浅红卡 */
.x-mood-警醒 {
background: linear-gradient(180deg, #fff8f7 0%, #fff0ef 100%);
border-color: rgba(255, 122, 122, 0.3);
}
.x-mood-警醒::after {
background: linear-gradient(90deg, #ff7a7a, #ffc1c1);
}
.x-mood-警醒 .x-tag {
background: rgba(255, 122, 122, 0.16);
color: #e56868;
}

/* 成长:浅绿卡 */
.x-mood-成长 {
background: linear-gradient(180deg, #f8fff9 0%, #eefaf0 100%);
border-color: rgba(124, 217, 146, 0.3);
}
.x-mood-成长::after {
background: linear-gradient(90deg, #7cd992, #c8f2d4);
}
.x-mood-成长 .x-tag {
background: rgba(124, 217, 146, 0.16);
color: #53a96a;
}

/* 认知:浅橙卡 */
.x-mood-认知 {
background: linear-gradient(180deg, #fffaf4 0%, #fff2e5 100%);
border-color: rgba(255, 184, 107, 0.34);
}
.x-mood-认知::after {
background: linear-gradient(90deg, #ffb86b, #ffe0b3);
}
.x-mood-认知 .x-tag {
background: rgba(255, 184, 107, 0.18);
color: #d98a2e;
}

/* 治愈:浅青绿卡 */
.x-mood-治愈 {
background: linear-gradient(180deg, #f7fcfa 0%, #edf7f3 100%);
border-color: rgba(148, 195, 177, 0.34);
}
.x-mood-治愈::after {
background: linear-gradient(90deg, #94c3b1, #d3eadf);
}
.x-mood-治愈 .x-tag {
background: rgba(148, 195, 177, 0.18);
color: #5f9c85;
}

/* =========================
深色模式
========================= */
.theme-dark .markdown-preview-view {
background: #1f1d1a;
}

.theme-dark .x-card {
background: #2d2925;
border-color: rgba(255, 236, 216, 0.1);
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28);
}

.theme-dark .x-quote {
color: #f4eee8;
}

.theme-dark .x-source {
color: #c8bfb7;
}

.theme-dark .x-meta {
color: #9aa8b4 !important;
}

.theme-dark .x-insight {
color: #ff9b92 !important;
border-top: 2px dashed rgba(255, 155, 146, 0.45) !important;
}

.theme-dark .x-tag {
background: rgba(255, 143, 134, 0.12);
color: #ffb1aa;
}

/* =========================
响应式
========================= */

/* 中大屏:3列 */
@media (max-width: 1500px) {
.markdown-preview-view {
max-width: 1450px;
}

.x-card-grid {
column-count: 3;
column-gap: 20px;
}
}

/* 中屏:2列 */
@media (max-width: 1100px) {
.markdown-preview-view {
max-width: 1100px;
padding-left: 14px;
padding-right: 14px;
}

.x-card-grid {
column-count: 2;
column-gap: 18px;
}
}

/* 小屏:1列 */
@media (max-width: 700px) {
.markdown-preview-view {
max-width: 100%;
padding-left: 8px;
padding-right: 8px;
}

.x-card-grid {
column-count: 1;
column-gap: 0;
padding: 8px 2px 18px 2px;
}

.x-card {
margin-bottom: 16px;
padding: 14px 12px 12px 12px;
border-radius: 16px;
}

.x-card::after {
top: 10px;
left: 12px;
width: 34px;
height: 4px;
}

.x-card-head {
padding-top: 6px;
}

.x-quote {
font-size: 14px;
line-height: 1.65;
}

.x-source {
font-size: 11.5px;
}

.x-meta,
.x-insight {
font-size: 11.5px !important;
}
}

最后在设置中启用css即可:

4. 具体使用

4.1. 创建图书

新建文章 -> 引用book模版 -> 填写属性即可,不再赘述。

4.2. 创建Flashcard

新建文章 -> 引用flashcard模版 -> 填写属性即可,也非常方便。

4.3. 最终展现

在Flashcard中的补充:

Book中的展示: