DataviewJS 高级用法
DataviewJS 是 Dataview 插件的高级模式,允许你使用完整的 JavaScript 编程能力来查询和展示笔记数据。相比 DQL 查询语言,DataviewJS 提供了更强的灵活性和表现力。
何时使用 DataviewJS
| 场景 | DQL | DataviewJS | 说明 |
|---|---|---|---|
| 简单列表/表格查询 | ✅ | ⭕ | DQL 更简洁直观 |
| 条件渲染 | ❌ | ✅ | 根据数据动态决定显示内容 |
| 跨笔记计算 | ❌ | ✅ | 对多笔记数据进行数学运算 |
| 交互式 UI | ❌ | ✅ | 按钮、表单、动态筛选 |
| 自定义 HTML | ❌ | ✅ | 完全控制渲染输出 |
| 复杂分组与嵌套 | 有限 | ✅ | 多级分组、嵌套表格 |
核心对象
DataviewJS 环境提供了以下内置对象:
javascript
// dv 对象 — Dataview 核心 API
dv.pages() // 获取所有页面
dv.page(path) // 获取单个页面
dv.current() // 获取当前页面
dv.date(text) // 解析日期
dv.duration(text) // 解析时长
dv.fileLink(path) // 创建文件链接
// 便捷方法
dv.header(level, text) // 渲染标题
dv.paragraph(text) // 渲染段落
dv.list(items) // 渲染列表
dv.table(headers, rows) // 渲染表格
dv.taskList(tasks) // 渲染任务列表
dv.markdownTable(headers, rows) // 生成 Markdown 表格文本
dv.span(text) // 渲染内联元素
dv.el(tag, text) // 渲染任意 HTML 元素基础示例
动态笔记仪表板
dataviewjs
const pages = dv.pages('"daily"')
.where(p => p.mood)
.sort(p => p.file.day, 'desc')
.limit(7);
if (pages.length === 0) {
dv.paragraph("暂无心情记录数据");
} else {
dv.header(3, "📊 最近 7 天心情追踪");
dv.table(
["日期", "心情", "天气", "关键词"],
pages.map(p => [
p.file.link,
p.mood,
p.weather || "—",
p.keywords ? p.keywords.join(", ") : "—"
])
);
// 统计心情分布
const moodCounts = {};
for (let p of pages) {
moodCounts[p.mood] = (moodCounts[p.mood] || 0) + 1;
}
dv.header(4, "心情分布");
const moodBar = Object.entries(moodCounts)
.map(([mood, count]) => `${mood} ${"█".repeat(count)} ${count}`)
.join("\n");
dv.paragraph(moodBar);
}项目进度仪表板
dataviewjs
const projects = dv.pages('"projects"')
.where(p => p.status);
dv.header(3, "🎯 项目进度总览");
// 分组显示
const groups = {
"🟢 进行中": projects.where(p => p.status === "active"),
"🟡 暂停": projects.where(p => p.status === "paused"),
"🔴 逾期": projects.where(p => p.deadline && p.deadline < dv.date("today") && p.status !== "done"),
"✅ 已完成": projects.where(p => p.status === "done")
};
for (let [label, group] of Object.entries(groups)) {
if (group.length === 0) continue;
dv.header(4, `${label} (${group.length})`);
dv.table(
["项目", "进度", "截止日期", "优先级"],
group.map(p => {
const daysLeft = p.deadline
? Math.floor((p.deadline - dv.date("today")).days)
: null;
const urgency = daysLeft !== null
? (daysLeft < 0 ? "🔴 逾期" : daysLeft < 7 ? "🟡 紧急" : "🟢 正常")
: "—";
return [
p.file.link,
p.progress ? `${p.progress}%` : "—",
p.deadline ? `${p.deadline} (${urgency})` : "—",
p.priority || "—"
];
})
);
}高级技巧
1. 交互式筛选
dataviewjs
const allPages = dv.pages('"notes"')
.where(p => p.tags)
.sort(p => p.file.name);
// 获取所有标签
const allTags = [...new Set(allPages.flatMap(p => p.tags))].sort();
// 创建筛选 UI
const container = dv.container || dv.el('div');
const filterDiv = container.createEl('div', {
cls: 'dvjs-filter-bar',
attr: { style: 'margin-bottom: 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap;' }
});
let selectedTag = null;
// 渲染标签按钮
for (let tag of allTags) {
const btn = filterDiv.createEl('button', {
text: tag,
cls: 'dvjs-tag-btn',
attr: {
style: 'padding: 4px 12px; border: 1px solid var(--background-modifier-border); border-radius: 4px; cursor: pointer; background: var(--background-secondary);'
}
});
btn.onclick = () => {
selectedTag = selectedTag === tag ? null : tag;
renderList();
// 更新按钮样式
filterDiv.querySelectorAll('button').forEach(b => {
b.style.background = b.textContent === selectedTag
? 'var(--interactive-accent)'
: 'var(--background-secondary)';
b.style.color = b.textContent === selectedTag
? 'var(--text-on-accent)'
: 'var(--text-normal)';
});
};
}
const listDiv = container.createEl('div');
function renderList() {
listDiv.empty();
const filtered = selectedTag
? allPages.where(p => p.tags && p.tags.includes(selectedTag))
: allPages;
if (filtered.length === 0) {
listDiv.createEl('p', { text: '没有匹配的笔记' });
return;
}
const ul = listDiv.createEl('ul');
for (let p of filtered) {
const li = ul.createEl('li');
const a = li.createEl('a', {
text: p.file.name,
attr: {
href: p.file.path,
class: 'internal-link'
}
});
if (p.tags && p.tags.length > 0) {
li.createEl('span', {
text: ` · ${p.tags.join(', ')}`,
attr: { style: 'color: var(--text-muted); font-size: 0.85em;' }
});
}
}
}
renderList();2. 数据可视化(进度条)
dataviewjs
const tasks = dv.pages('"tasks"')
.where(p => p.file.tasks)
.file.tasks
.where(t => !t.completed);
const byPriority = {
"🔴 高": tasks.where(t => t.priority === "high"),
"🟡 中": tasks.where(t => t.priority === "medium"),
"🟢 低": tasks.where(t => t.priority === "low" || !t.priority)
};
dv.header(3, "📋 任务优先级分布");
for (let [label, group] of Object.entries(byPriority)) {
if (group.length === 0) continue;
const percent = Math.round(group.length / tasks.length * 100);
const bar = "█".repeat(Math.ceil(percent / 5)) +
"░".repeat(20 - Math.ceil(percent / 5));
dv.paragraph(`${label} \`${bar}\` ${group.length} (${percent}%)`);
}
// 总进度
const allTasks = dv.pages('"tasks"').file.tasks;
const completed = allTasks.where(t => t.completed).length;
const total = allTasks.length;
const overallPercent = total > 0 ? Math.round(completed / total * 100) : 0;
dv.header(3, `📊 总进度: ${overallPercent}%`);
const filledBlocks = Math.floor(overallPercent / 2);
const progressBar = "▓".repeat(filledBlocks) +
"░".repeat(50 - filledBlocks);
dv.paragraph(`\`${progressBar}\` ${completed}/${total}`);3. 笔记热力图
dataviewjs
// 生成类似 GitHub 贡献图的热力图
const dailyNotes = dv.pages('"daily"');
const today = dv.date("today");
const startDate = today.minus({ days: 90 });
// 按日期分组
const noteCounts = {};
for (let p of dailyNotes) {
if (p.file.day) {
const dateStr = p.file.day.toFormat("yyyy-MM-dd");
noteCounts[dateStr] = (noteCounts[dateStr] || 0) + 1;
}
}
dv.header(3, "📈 最近 90 天笔记热力图");
const container = dv.el('div', '');
const grid = container.createEl('div', {
attr: {
style: 'display: grid; grid-template-columns: repeat(15, 1fr); gap: 3px; max-width: 600px;'
}
});
for (let d = 0; d < 90; d++) {
const date = startDate.plus({ days: d });
const dateStr = date.toFormat("yyyy-MM-dd");
const count = noteCounts[dateStr] || 0;
// 热度等级 0-4
let level = 0;
if (count >= 5) level = 4;
else if (count >= 3) level = 3;
else if (count >= 2) level = 2;
else if (count >= 1) level = 1;
const colors = [
'var(--background-secondary)',
'#9be9a8',
'#40c463',
'#30a14e',
'#216e39'
];
const cell = grid.createEl('div', {
attr: {
title: `${dateStr}: ${count} 篇`,
style: `width: 100%; aspect-ratio: 1; border-radius: 2px; background: ${colors[level]};`
}
});
}
// 图例
const legend = container.createEl('div', {
attr: { style: 'margin-top: 8px; display: flex; align-items: center; gap: 4px; font-size: 0.8em; color: var(--text-muted);' }
});
legend.createEl('span', { text: '少' });
for (let c of ['var(--background-secondary)', '#9be9a8', '#40c463', '#30a14e', '#216e39']) {
legend.createEl('div', {
attr: { style: `width: 12px; height: 12px; border-radius: 2px; background: ${c};` }
});
}
legend.createEl('span', { text: '多' });4. 读书统计报告
dataviewjs
const books = dv.pages('"books"')
.where(p => p.author);
dv.header(3, "📚 阅读统计报告");
// 基本统计
const total = books.length;
const finished = books.where(p => p.status === "read").length;
const reading = books.where(p => p.status === "reading").length;
const toRead = books.where(p => p.status === "to-read").length;
dv.table(
["统计项", "数量"],
[
["📚 总藏书量", total],
["✅ 已读完", finished],
["📖 阅读中", reading],
["📋 待阅读", toRead],
["📊 完读率", total > 0 ? `${Math.round(finished / total * 100)}%` : "—"]
]
);
// 按作者统计
const authorStats = {};
for (let b of books.where(p => p.status === "read")) {
if (b.author) {
authorStats[b.author] = (authorStats[b.author] || 0) + 1;
}
}
const topAuthors = Object.entries(authorStats)
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
if (topAuthors.length > 0) {
dv.header(4, "🏆 最多产作者 Top 5");
dv.table(
["作者", "已读书数"],
topAuthors
);
}
// 按评分排序
const rated = books.where(p => p.rating)
.sort(p => -p.rating)
.limit(10);
if (rated.length > 0) {
dv.header(4, "⭐ 评分最高 Top 10");
dv.table(
["书名", "作者", "评分", "读完日期"],
rated.map(p => [
p.file.link,
p.author || "—",
"⭐".repeat(p.rating),
p.finished_date || "—"
])
);
}5. 自动化周报生成
dataviewjs
const today = dv.date("today");
const weekStart = today.minus({ days: today.weekday - 1 });
const weekEnd = weekStart.plus({ days: 6 });
dv.header(2, `📋 周报 ${weekStart.toFormat("yyyy-MM-dd")} ~ ${weekEnd.toFormat("yyyy-MM-dd")}`);
// 本周新增笔记
const newNotes = dv.pages()
.where(p => p.file.ctime >= weekStart && p.file.ctime <= weekEnd.plus({ days: 1 }))
.sort(p => p.file.ctime, 'desc');
dv.header(3, `📝 本周新增笔记 (${newNotes.length})`);
if (newNotes.length > 0) {
dv.table(
["笔记", "创建时间", "分类"],
newNotes.map(p => [
p.file.link,
p.file.ctime.toFormat("MM-dd HH:mm"),
p.category || p.tags?.[0] || "—"
])
);
}
// 本周修改笔记
const modifiedNotes = dv.pages()
.where(p => p.file.mtime >= weekStart && p.file.mtime <= weekEnd.plus({ days: 1 })
&& p.file.ctime < weekStart)
.sort(p => p.file.mtime, 'desc');
dv.header(3, `✏️ 本周修改笔记 (${modifiedNotes.length})`);
if (modifiedNotes.length > 0) {
dv.list(modifiedNotes.limit(10).map(p =>
`${p.file.link} — ${p.file.mtime.toFormat("MM-dd")}`
));
}
// 本周完成任务
const tasks = dv.pages().file.tasks
.where(t => t.completed && t.completion >= weekStart && t.completion <= weekEnd.plus({ days: 1 }));
dv.header(3, `✅ 本周完成任务 (${tasks.length})`);
if (tasks.length > 0) {
dv.taskList(tasks, false);
}
// 本周待办
const upcomingTasks = dv.pages().file.tasks
.where(t => !t.completed)
.sort(t => t.text);
dv.header(3, `📋 待办任务 (${upcomingTasks.length})`);
dv.taskList(upcomingTasks.limit(15), false);性能优化
避免全库扫描
javascript
// ❌ 慢:扫描所有页面
const all = dv.pages().where(p => p.file.folder === "projects");
// ✅ 快:限定路径范围
const projects = dv.pages('"projects"');
// ✅ 更快:限定路径 + 过滤条件组合
const active = dv.pages('"projects"').where(p => p.status === "active");使用 limit 减少处理量
javascript
// ✅ 先过滤再排序再限制
const recent = dv.pages('"daily"')
.sort(p => p.file.day, 'desc')
.limit(30);
// ❌ 避免:处理所有数据后再 slice
const all = dv.pages('"daily"').sort(p => p.file.day, 'desc');
const recent = all.slice(0, 30); // 已经处理了全部数据缓存计算结果
dataviewjs
// 使用 dataviewjs 的缓存机制
const cache = dv.current().cache || {};
if (!cache.stats) {
cache.stats = {
total: dv.pages().length,
computed: dv.date("today").toFormat("yyyy-MM-dd")
};
}
dv.paragraph(`总笔记数:${cache.stats.total}(计算于 ${cache.stats.computed})`);注意事项
DataviewJS 安全提示
- DataviewJS 会执行任意 JavaScript 代码,仅在你信任的笔记中启用
- 不要在共享仓库中使用未经验证的 DataviewJS 代码
- 复杂查询可能影响性能,建议使用
limit()控制结果数量 - DataviewJS 代码在每次渲染时执行,避免在循环中做重复计算
相关文档
- Dataview 实战指南 — DQL 查询语言入门
- 高级 Dataview 查询案例 — DQL 进阶查询
- Dataview 速查表 — 语法快速参考
- Properties 与 Dataview 联动 — 属性与查询联动
- 笔记统计 — 数据统计可视化