<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Obsidian &#8211; 秋山砚语</title>
	<atom:link href="https://gsfall.cn/archives/tag/obsidian/feed" rel="self" type="application/rss+xml" />
	<link>https://gsfall.cn</link>
	<description>GushanFall&#039;s Blog</description>
	<lastBuildDate>Mon, 02 Feb 2026 04:45:52 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://gsfall.cn/wp-content/uploads/2025/04/cropped-icon-32x32.png</url>
	<title>Obsidian &#8211; 秋山砚语</title>
	<link>https://gsfall.cn</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Obsidian 进阶：手把手教你搭建“交互式”电影海报墙</title>
		<link>https://gsfall.cn/archives/169</link>
					<comments>https://gsfall.cn/archives/169#respond</comments>
		
		<dc:creator><![CDATA[GushanFall]]></dc:creator>
		<pubDate>Sun, 01 Feb 2026 08:09:01 +0000</pubDate>
				<category><![CDATA[Obsidian]]></category>
		<category><![CDATA[已分类]]></category>
		<category><![CDATA[Dataview]]></category>
		<category><![CDATA[教程]]></category>
		<guid isPermaLink="false">https://gsfall.cn/?p=169</guid>

					<description><![CDATA[为了更好地管理个人的观影记录，利用 DataviewJS 搭建了一个可视化的海报墙。相比于简单的文字列表，这个界面不仅更加直观，还具备了类似 App 的交互查询功能。]]></description>
										<content:encoded><![CDATA[
<p>为了更好地管理个人的观影记录，利用 <strong>DataviewJS</strong> 搭建了一个可视化的海报墙。相比于简单的文字列表，这个界面不仅更加直观，还具备了类似 App 的交互查询功能。</p>



<h1 class="wp-block-heading">最终效果展示</h1>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="936" src="https://gsfall.cn/wp-content/uploads/2026/02/image-1024x936.png" alt="" class="wp-image-170" srcset="https://gsfall.cn/wp-content/uploads/2026/02/image-1024x936.png 1024w, https://gsfall.cn/wp-content/uploads/2026/02/image-300x274.png 300w, https://gsfall.cn/wp-content/uploads/2026/02/image-768x702.png 768w, https://gsfall.cn/wp-content/uploads/2026/02/image-1536x1404.png 1536w, https://gsfall.cn/wp-content/uploads/2026/02/image.png 1700w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>摒弃了传统的表格形式，采用<strong>网格卡片布局</strong>来展示每一部电影。能够清晰展示以下核心信息：</p>



<ul class="wp-block-list">
<li><strong>视觉封面</strong>：自动加载高清电影海报。</li>



<li><strong>双重评分</strong>：同时展示“豆瓣评分”（大众参考）和“个人推荐”（私人喜好）。</li>



<li><strong>剧情与评价</strong>：简介和个人短评通过独立的滚动框展示，既保证了内容完整，又维持了界面的整洁统一。</li>
</ul>



<p>为了方便在庞大的片单中快速找到想看的内容，在顶部设计了四行<strong>动态工具栏</strong>：</p>



<ul class="wp-block-list">
<li><strong>排序功能</strong>：支持按“添加时间”、“上映年份”或“评分高低”一键排序。</li>



<li><strong>状态筛选</strong>：快速切换“已阅”库存和“想看”清单。</li>



<li><strong>分类检索</strong>：自动统计库中的热门标签（如科幻、悬疑），点击即可筛选对应类型的影片。</li>



<li><strong>年代回溯</strong>：可以按年份精准查找老片。</li>
</ul>



<h1 class="wp-block-heading">准备工作</h1>



<p>在开始编写代码之前，我们需要先搭建好基础环境，并统一电影笔记的格式。这一步至关重要，它决定了后续的脚本能否正确读取到数据。</p>



<h2 class="wp-block-heading">核心插件：Dataview</h2>



<p>这是整个影视库的引擎。我们需要通过它来索引笔记并渲染复杂的交互界面。</p>



<ul class="wp-block-list">
<li><strong>安装</strong>：在 Obsidian 社区插件市场搜索 <code>Dataview</code> 并安装启用。</li>



<li><strong>关键设置</strong>：打开 Dataview 设置面板，<strong>务必开启</strong> <code>Enable JavaScript Queries</code> 选项。</li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="434" src="https://gsfall.cn/wp-content/uploads/2026/02/image-1-1024x434.png" alt="" class="wp-image-171" srcset="https://gsfall.cn/wp-content/uploads/2026/02/image-1-1024x434.png 1024w, https://gsfall.cn/wp-content/uploads/2026/02/image-1-300x127.png 300w, https://gsfall.cn/wp-content/uploads/2026/02/image-1-768x325.png 768w, https://gsfall.cn/wp-content/uploads/2026/02/image-1.png 1185w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>注意</strong>：因为我们使用的是 <code>dataviewjs</code> 脚本，如果不开启此选项，代码将无法运行。</p>



<h2 class="wp-block-heading">建立目录</h2>



<p>建议为电影笔记建立一个独立的文件夹，例如 <code>Gallery/Movies</code>。 这样做的好处是方便脚本划定索引范围，避免扫描到无关的日常笔记，提高性能。</p>



<h2 class="wp-block-heading">标准化元数据（YAML）</h2>



<p>为了让脚本能识别电影的封面、评分和状态，每部电影的笔记都需要包含特定的 <strong>YAML Frontmatter</strong>（即笔记开头的属性区域）。</p>



<p>这是我目前使用的标准模板，可以将其保存为 Obsidian 的模板文件，每次新建电影时直接插入：</p>



<pre class="wp-block-code"><code>cover: "&#91;&#91;attachments/Pasted image 20260125144605.png]]"
title: 死无对证
original_title: The Invisible Witness
year: 2018
director: 斯蒂法诺·摩尔蒂尼
rating: 7.3
recommend: 4
status: 已看
tags:
  - 悬疑
  - 惊悚
  - 犯罪
plot: 阿德里亚诺是一名事业有成的企业家，直到那天他在酒店房间醒来，发现情人劳拉死在浴室里，而自己手握凶器。为了洗脱罪名，他请来了从未失手的金牌律师弗吉尼亚，试图在短短几小时内理清真相……
comment: 剧情复刻了《看不见的客人》，细节处理挺好，节奏紧凑，层层剥茧，最后的反转也非常精彩。《消失的她》应该是有借鉴这部影片。</code></pre>



<p><strong>关键字段说明</strong></p>



<ul class="wp-block-list">
<li><code>cover</code>: 封面文件的存放位置</li>



<li><code>rating</code>: 豆瓣评分，满分 10 分</li>



<li><code>recommend</code>: 个人推荐程度，满分 5 分</li>



<li><code>plot</code>: 电影简介</li>



<li><code>comment</code>: 个人评价</li>
</ul>



<h1 class="wp-block-heading">注入灵魂样式（CSS）</h1>



<p>有了数据之后，默认的 Dataview 输出还只是普通的列表。为了实现“海报墙”的视觉效果，我们需要通过 CSS 对页面元素进行重构。</p>



<p>这一步将决定影视库的“颜值”。</p>



<h2 class="wp-block-heading">启用 CSS 片段</h2>



<p>Obsidian 提供了非常方便的 <code>CSS Snippets</code>（代码片段）功能，允许我们在不修改主题的情况下自定义样式。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="930" src="https://gsfall.cn/wp-content/uploads/2026/02/image-2-1024x930.png" alt="" class="wp-image-172" srcset="https://gsfall.cn/wp-content/uploads/2026/02/image-2-1024x930.png 1024w, https://gsfall.cn/wp-content/uploads/2026/02/image-2-300x272.png 300w, https://gsfall.cn/wp-content/uploads/2026/02/image-2-768x698.png 768w, https://gsfall.cn/wp-content/uploads/2026/02/image-2-1536x1395.png 1536w, https://gsfall.cn/wp-content/uploads/2026/02/image-2.png 1658w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li><strong>操作步骤</strong>：
<ol class="wp-block-list">
<li>进入 <code>设置</code> -> <code>外观</code> -> <code>CSS 代码片段</code>。</li>



<li>点击右侧的文件夹图标，打开 snippets 文件夹。</li>



<li>新建一个文本文件，重命名为 <code>media-cards.css</code>。</li>



<li>将下方的代码粘贴进去并保存。</li>



<li>回到 Obsidian 设置页，点击刷新按钮，并<strong>启用</strong> <code>media-cards.css</code>。</li>
</ol>
</li>
</ul>



<h2 class="wp-block-heading">样式代码</h2>



<pre class="wp-block-code"><code>/* --- media-cards.css (上下布局+两端对齐版) --- */

.media-gallery {
    display: grid;
    /* 保持较宽的卡片宽度 */
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 20px; margin-top: 20px; padding-bottom: 20px;
}

.media-card {
    position: relative;
    background-color: var(--background-secondary);
    border-radius: 12px;
    border: 1px solid var(--background-modifier-border);
    transition: all 0.3s ease;
    display: flex; flex-direction: column;
    overflow: hidden;
    text-decoration: none !important; color: var(--text-normal) !important;
}
.media-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
    border-color: var(--interactive-accent);
}

/* 封面区域 */
.media-card-cover {
    width: 100%; aspect-ratio: 2 / 3; 
    background-size: cover; background-position: center top;
    position: relative; border-bottom: 1px solid var(--background-modifier-border);
}
.media-rank {
    position: absolute; top: 8px; left: 8px;
    background: rgba(0, 0, 0, 0.65); color: rgba(255, 255, 255, 0.9);
    padding: 2px 8px; border-radius: 6px; font-size: 0.75em; font-weight: 600;
    backdrop-filter: blur(4px);
}
.media-score-badge {
    position: absolute; bottom: 8px; right: 8px;
    background: var(--interactive-accent); color: var(--text-on-accent);
    padding: 2px 6px; border-radius: 4px; font-weight: 800; font-size: 0.9em;
    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
/* 编辑按钮 */
.media-edit-btn {
    position: absolute; top: 8px; right: 8px; opacity: 0;
    transition: opacity 0.2s ease; background: rgba(var(--mono-rgb-100), 0.8);
    padding: 4px; border-radius: 50%; cursor: pointer; line-height: 1;
}
.media-card:hover .media-edit-btn { opacity: 1; }

/* 内容区域 */
.media-card-content {
    padding: 14px;
    flex-grow: 1; display: flex; flex-direction: column; 
    gap: 10px; /* 增加各模块之间的间距 */
}

/* 标题部分 */
.media-title-block {
    margin-bottom: 2px;
}
.media-title-cn { font-size: 1.1em; font-weight: 700; line-height: 1.3; color: var(--text-normal); margin-bottom: 2px; }
.media-title-en { 
    font-size: 0.8em; color: var(--text-muted); line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- 1. 评分行 (两端对齐) --- */
.media-rating-row {
    display: flex;
    justify-content: space-between; /* 关键：左边文字，右边星星 */
    align-items: center;
    font-size: 0.9em;
    height: 24px;
}
.media-label-rating {
    color: var(--text-muted);
    font-weight: bold;
}
/* 星星/红心容器 */
.media-stars-container {
    /* 强制使用 Emoji 字体，确保对齐 */
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
    letter-spacing: 2px;
}

/* --- 2. 文本块 (上下布局) --- */
.media-text-block {
    display: flex;
    flex-direction: column; /* 关键：垂直排列 */
    gap: 4px;
}
.media-label-text {
    font-size: 0.85em;
    font-weight: bold;
    color: var(--text-muted);
}
.media-text-content {
    background: var(--background-primary);
    padding: 8px;
    border-radius: 6px;
    font-size: 0.85em;
    color: var(--text-faint);
    line-height: 1.5;

    /* 高度控制 */
    height: 64px;       
    overflow-y: auto;
    word-wrap: break-word;
}
/* 滚动条 */
.media-text-content::-webkit-scrollbar { width: 3px; }
.media-text-content::-webkit-scrollbar-thumb {
    background-color: var(--text-muted); border-radius: 3px; opacity: 0.3;
}

/* 底部标签 */
.media-tags { margin-top: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.media-tag {
    background: var(--background-modifier-hover); padding: 1px 6px;
    border-radius: 4px; font-size: 0.7em; color: var(--text-muted);
}
.media-meta-row {
    display: flex; justify-content: space-between; align-items: center;
    border-top: 1px solid var(--background-modifier-border); padding-top: 8px;
    font-size: 0.75em; color: var(--text-muted);
}
.media-meta-item {
    display: flex; align-items: center; gap: 4px;
    max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- 交互式工具栏样式 --- */

/* 工具栏容器 */
.media-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 16px;
    padding: 8px 12px;
    background: var(--background-secondary);
    border-radius: 8px;
    border: 1px solid var(--background-modifier-border);
    align-items: center;
}

/* 按钮组标签 (例如 "排序: ") */
.media-toolbar-label {
    font-size: 0.85em;
    font-weight: bold;
    color: var(--text-muted);
    margin-right: 4px;
}

/* 按钮本体 */
.media-btn {
    background: transparent;
    border: 1px solid var(--background-modifier-border);
    color: var(--text-muted);
    padding: 4px 10px;
    border-radius: 12px;
    font-size: 0.8em;
    cursor: pointer;
    transition: all 0.2s ease;

    /* 【新增】强制使用 Emoji 字体，解决按钮上红心变黑的问题 */
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}

.media-btn:hover {
    background: var(--background-modifier-hover);
    color: var(--text-normal);
}

/* 激活状态的按钮 (高亮) */
.media-btn.active {
    background: var(--interactive-accent);
    color: var(--text-on-accent);
    border-color: var(--interactive-accent);
    font-weight: bold;
}</code></pre>



<p>当然这部分代码可以按照个人的喜好进行修改，如果不擅长代码，可以交给现在的大模型来修改，只需提出自己的要求。</p>



<h1 class="wp-block-heading">构建交互引擎（DataviewJS）</h1>



<p>在你的“影音库”笔记中（或者任意你想展示海报墙的笔记里），先添加属性，表明要使用前面启用的 css</p>



<pre class="wp-block-code"><code>cssclasses:
  - media-cards.css</code></pre>



<p>然后在添加 Dataview 代码块，并在代码块中粘贴以下代码</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="87" src="https://gsfall.cn/wp-content/uploads/2026/02/image-3-1024x87.png" alt="" class="wp-image-173" srcset="https://gsfall.cn/wp-content/uploads/2026/02/image-3-1024x87.png 1024w, https://gsfall.cn/wp-content/uploads/2026/02/image-3-300x26.png 300w, https://gsfall.cn/wp-content/uploads/2026/02/image-3-768x65.png 768w, https://gsfall.cn/wp-content/uploads/2026/02/image-3.png 1315w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<pre class="wp-block-code"><code>// ================= &#x2699; 配置区域 =================
const sourceFolder = '"Gallery/Movies"'; 
// ===============================================

// 1. 定义全局状态
let state = {
    sortKey: 'year',      // 排序依据
    sortOrder: 'desc',    // 排序方向
    filterTag: 'all',     // 标签筛选
    filterYear: 'all',    // 年份筛选
    filterStatus: 'all'   // 【新增】状态筛选 (all | watched | todo)
};

const root = dv.el("div", "", { cls: "media-app-container" });

// --- 图片处理 ---
function getImageUrl(imagePath) {
    if (!imagePath) return "";
    if (imagePath.path) {
        const file = app.metadataCache.getFirstLinkpathDest(imagePath.path, "");
        return file ? app.vault.getResourcePath(file) : "";
    }
    let str = String(imagePath);
    if (str.startsWith('!&#91;')) {
        let urlMatch = str.match(/\((https?:\/\/.*?)\)/);
        if (urlMatch) str = urlMatch&#91;1];
        else {
             let linkMatch = str.match(/\&#91;\&#91;(.*?)(?:\|.*)?\]\]/);
             if (linkMatch) str = linkMatch&#91;1];
        }
    }
    if (str.startsWith('http')) {
        return `https://images.weserv.nl/?url=${encodeURIComponent(str)}`;
    }
    let cleanName = str.replace(/&#91;\&#91;\]"]/g, '');
    const file = app.metadataCache.getFirstLinkpathDest(cleanName, "");
    return file ? app.vault.getResourcePath(file) : "";
}

function getHearts(index) {
    if (!index) return ""; 
    let score = Math.min(Math.max(index, 0), 5); 
    const emojiStyle = "font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;";
    return `&lt;span style="letter-spacing:2px; font-size:0.9em; ${emojiStyle}"&gt;` + "&#x2764;".repeat(score) + "&#x1f90d;".repeat(5 - score) + `&lt;/span&gt;`;
}

// 统计所有标签
function getTopTags(pages) {
    let tagMap = new Map();
    pages.forEach(p =&gt; {
        if (!p.tags) return;
        let tags = typeof p.tags === 'string' ? &#91;p.tags] : p.tags;
        tags.forEach(t =&gt; {
            if (t === 'Movie' || t === '电影' || t.includes('Media')) return;
            tagMap.set(t, (tagMap.get(t) || 0) + 1);
        });
    });
    return Array.from(tagMap.entries())
        .sort((a, b) =&gt; b&#91;1] - a&#91;1])
        // .slice(0, 8)
        .map(entry =&gt; entry&#91;0]);
}

// 提取所有存在的年份
function getYears(pages) {
    let years = new Set();
    pages.forEach(p =&gt; {
        if (p.year) years.add(p.year);
    });
    return Array.from(years).sort((a, b) =&gt; b - a);
}

// --- 渲染引擎 ---
function render() {
    root.innerHTML = "";

    // 1. 获取原始数据
    let allPages = dv.pages(sourceFolder)
        .filter(p =&gt; !p.file.name.includes("Template") &amp;&amp; p.file.name !== "未命名");

    const topTags = getTopTags(allPages);
    const allYears = getYears(allPages);

    // 2. 执行多重筛选 (Status + Tag + Year)
    let displayPages = allPages;

    // A. 【新增】状态筛选
    if (state.filterStatus === 'watched') {
        displayPages = displayPages.filter(p =&gt; {
            const s = (p.status || p.状态 || "").toLowerCase();
            return s.includes('watched') || s.includes('已阅') || s.includes('已看');
        });
    } else if (state.filterStatus === 'todo') {
        displayPages = displayPages.filter(p =&gt; {
            const s = (p.status || p.状态 || "").toLowerCase();
            return s.includes('wish') || s.includes('todo') || s.includes('想看');
        });
    }

    // B. 标签筛选
    if (state.filterTag !== 'all') {
        displayPages = displayPages.filter(p =&gt; {
            if (!p.tags) return false;
            let tags = typeof p.tags === 'string' ? &#91;p.tags] : p.tags;
            return tags.includes(state.filterTag);
        });
    }

    // C. 年份筛选
    if (state.filterYear !== 'all') {
        displayPages = displayPages.filter(p =&gt; p.year == state.filterYear);
    }

    // 3. 执行排序
    if (state.sortKey === 'year') {
        displayPages = displayPages.sort(p =&gt; p.year, state.sortOrder);
    } else if (state.sortKey === 'rating') {
        displayPages = displayPages.sort(p =&gt; p.rating, state.sortOrder);
    } else if (state.sortKey === 'recommend') {
        displayPages = displayPages.sort(p =&gt; (p.recommend || p&#91;"推荐指数"] || 0), state.sortOrder);
    } else if (state.sortKey === 'date') {
        displayPages = displayPages.sort(p =&gt; p.file.ctime, state.sortOrder);
    } else {
        displayPages = displayPages.sort(p =&gt; p.file.name, state.sortOrder);
    }

    // 4. 绘制工具栏
    const toolbar = document.createElement("div");
    toolbar.className = "media-toolbar";
    toolbar.style.flexDirection = "column"; 
    toolbar.style.alignItems = "flex-start";

    // --- 第一行：排序控制 ---
    const rowSort = createToolbarRow("排序:");
    createBtn(rowSort, "&#x1f552; 添加时间", () =&gt; { state.sortKey = 'date'; render(); }, state.sortKey === 'date');
    createBtn(rowSort, "&#x1f4c5; 上映年份", () =&gt; { state.sortKey = 'year'; render(); }, state.sortKey === 'year');
    createBtn(rowSort, "&#x2b50; 评分", () =&gt; { state.sortKey = 'rating'; render(); }, state.sortKey === 'rating');
    createBtn(rowSort, "&#x2764; 推荐", () =&gt; { state.sortKey = 'recommend'; render(); }, state.sortKey === 'recommend');

    const spacer = document.createElement("span"); spacer.style.flexGrow = "1"; rowSort.appendChild(spacer);
    const orderText = state.sortOrder === 'desc' ? "&#x2b07; 降序" : "&#x2b06; 升序";
    createBtn(rowSort, orderText, () =&gt; { state.sortOrder = state.sortOrder === 'desc' ? 'asc' : 'desc'; render(); }, false);
    toolbar.appendChild(rowSort);

    // --- 第二行：状态筛选 【新增】 ---
    const rowStatus = createToolbarRow("状态:");
    createBtn(rowStatus, "全部", () =&gt; { state.filterStatus = 'all'; render(); }, state.filterStatus === 'all');
    createBtn(rowStatus, "&#x2705; 已看", () =&gt; { state.filterStatus = 'watched'; render(); }, state.filterStatus === 'watched');
    createBtn(rowStatus, "&#x1f4dd; 想看", () =&gt; { state.filterStatus = 'todo'; render(); }, state.filterStatus === 'todo');
    toolbar.appendChild(rowStatus);

    // --- 第三行：标签筛选 ---
    const rowTag = createToolbarRow("类型:");
    createBtn(rowTag, "全部", () =&gt; { state.filterTag = 'all'; render(); }, state.filterTag === 'all');
    topTags.forEach(tag =&gt; {
        createBtn(rowTag, tag, () =&gt; { state.filterTag = tag; render(); }, state.filterTag === tag);
    });
    toolbar.appendChild(rowTag);

    // --- 第四行：年份筛选 ---
    const rowYear = createToolbarRow("年份:");
    createBtn(rowYear, "全部", () =&gt; { state.filterYear = 'all'; render(); }, state.filterYear === 'all');
    allYears.forEach(y =&gt; {
        createBtn(rowYear, String(y), () =&gt; { state.filterYear = y; render(); }, state.filterYear == y);
    });
    toolbar.appendChild(rowYear);

    root.appendChild(toolbar);

    // 5. 绘制卡片
    if (displayPages.length === 0) {
        root.createEl("p", { text: "&#x26a0; 没有找到符合条件的电影", style: "color:var(--text-muted); padding:20px;" });
        return;
    }

    const container = document.createElement("div");
    container.className = "media-gallery"; 

    displayPages.forEach((page) =&gt; {
        let coverUrl = getImageUrl(page.cover || page&#91;"封面"]);
        let title = page.title || page&#91;"中文名"] || page.file.name;
        let subTitle = page.original_title || page&#91;"英文名"] || ""; 
        let rating = page.rating || page&#91;"评分"] || 0;
        let year = page.year || page&#91;"年份"] || "-";
        let director = page.director || page&#91;"导演"] || "未知";
        let status = page.status || page&#91;"状态"] || "已阅";
        let plot = page.plot || page&#91;"剧情"] || page&#91;"剧情简介"];
        let comment = page.comment || page&#91;"评价"] || page&#91;"个人评价"];
        let recommendIndex = page.recommend || page&#91;"推荐指数"] || 0;
        let displayTags = page.tags || &#91;];
        if (typeof displayTags === 'string') displayTags = &#91;displayTags];

        const tagsHtml = displayTags
            .filter(t =&gt; !t.includes("Movie") &amp;&amp; !t.includes("电影")) 
            .slice(0, 3) 
            .map(t =&gt; `&lt;span class="media-tag"&gt;${t}&lt;/span&gt;`)
            .join('');

        const card = document.createElement("div");
        card.className = "media-card";

        card.innerHTML = `
            &lt;div class="media-card-cover" style="background-image: url('${coverUrl}');"&gt;
                &lt;div class="media-rank"&gt;${year}&lt;/div&gt;
                &lt;div class="media-score-badge"&gt;${rating}&lt;/div&gt;
                &lt;div class="media-edit-btn" title="打开笔记"&gt;&#x270f;&lt;/div&gt;
            &lt;/div&gt;

            &lt;div class="media-card-content"&gt;
                &lt;div class="media-title-block"&gt;
                    &lt;div class="media-title-cn"&gt;${title}&lt;/div&gt;
                    &lt;div class="media-title-en"&gt;${subTitle}&lt;/div&gt;
                &lt;/div&gt;

                ${recommendIndex &gt; 0 ? `
                &lt;div class="media-rating-row"&gt;
                    &lt;span class="media-label-rating"&gt;推荐指数&lt;/span&gt;
                    &lt;span class="media-stars-container"&gt;${getHearts(recommendIndex)}&lt;/span&gt;
                &lt;/div&gt;` : ''}

                ${plot ? `
                &lt;div class="media-text-block"&gt;
                    &lt;div class="media-label-text"&gt;剧情简介&lt;/div&gt;
                    &lt;div class="media-text-content"&gt;${plot}&lt;/div&gt;
                &lt;/div&gt;` : ''}

                ${comment ? `
                &lt;div class="media-text-block"&gt;
                    &lt;div class="media-label-text"&gt;个人评价&lt;/div&gt;
                    &lt;div class="media-text-content"&gt;${comment}&lt;/div&gt;
                &lt;/div&gt;` : ''}

                &lt;div class="media-tags"&gt;${tagsHtml}&lt;/div&gt;

                &lt;div class="media-meta-row"&gt;
                    &lt;div class="media-meta-item" title="导演"&gt;&#x1f3ac; ${director}&lt;/div&gt;
                    &lt;div class="media-meta-item" title="状态"&gt;&#x1f3f7; ${status}&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        `;
        card.onclick = (e) =&gt; {
            e.preventDefault();
            app.workspace.openLinkText(page.file.path, "", false);
        };
        container.appendChild(card);
    });
    root.appendChild(container);
}

// 辅助：创建工具栏的一行
function createToolbarRow(labelText) {
    const row = document.createElement("div");
    row.style.display = "flex";
    row.style.gap = "8px";
    row.style.width = "100%";
    row.style.marginBottom = "8px";
    row.style.borderBottom = "1px solid var(--background-modifier-border)";
    row.style.paddingBottom = "8px";
    row.style.flexWrap = "wrap";

    const label = document.createElement("span");
    label.className = "media-toolbar-label";
    label.innerText = labelText;
    row.appendChild(label);
    return row;
}

function createBtn(parent, text, onClick, isActive) {
    const btn = document.createElement("button");
    btn.className = "media-btn" + (isActive ? " active" : "");
    btn.innerText = text;
    btn.onclick = onClick;
    parent.appendChild(btn);
}

render();</code></pre>



<p>请务必修改代码第一行的 <code>sourceFolder</code>，将其替换为你存放电影笔记的实际文件夹路径（例如 <code>"MyData/Movies"</code>）。</p>



<p>如果一切正常，当你退出代码编辑模式，切换到<strong>预览模式（Reading View）</strong>时，你应该能看到：</p>



<ol class="wp-block-list">
<li>顶部出现了四行工具栏（排序、状态、类型、年份）。</li>



<li>下方的卡片整齐排列，海报图片已经加载出来。</li>



<li>点击顶部的按钮，下方的卡片会即时发生变化。</li>
</ol>



<p><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 至此，你的个人交互式影视库就已经搭建完成了！</strong></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://gsfall.cn/archives/169/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
