



























l.section?.subpath === "Time Log"- hh:mm do what #tag
---date: <% tp.file.creation_date("YYYY-MM-DD") %>NO.week: <% window.moment().format("GGGG-[W]WW") %>---月记名为"YYYY-MM"格式,不是的话代码无法自动提取,需要你在代码中自行指定月份。
代码如下,注释挺良好的,记得改下文件夹路径之类的,其他看不懂的,GPT都懂:
/***********************
* 工具函数
***********************/
function parseTime(t) {
const [h, m] = t.split(":").map(Number);
return h * 60 + m;
}
function getMonth(day) {
return `${day.year}-${String(day.month).padStart(2, "0")}`;
}
/***********************
* 当前月(从月记文件名取)
***********************/
const currentMonth = dv.current().file.name.match(/\d{4}-\d{2}/)?.[0];
/***********************
* 数据结构,不用管,自动填入
***********************/
// daily[date][root] = { total, children }
// weekly[week][root] = { total, children }
// monthly[root] = { total, children }
let daily = {};
let weekly = {};
let monthly = {};
/***********************
* 只有在能识别月份时才执行统计
***********************/
if (currentMonth) {
/***********************
* 扫描本月所有日记,在“日记”文件夹下
***********************/
for (const page of dv.pages('"日记"')) {
if (!page.date) continue;
const pageDate = dv.date(page.date);
if (getMonth(pageDate) !== currentMonth) continue;
const dateKey = dv.date(page.date).toFormat("yyyy-MM-dd");
const weekKey = page["NO.week"] ?? "Unknown";
daily[dateKey] ??= {};
weekly[weekKey] ??= {};
const logs = page.file.lists.filter(
l => l.section?.subpath === "Time Log" //Time Log是你自定义的节名
);
for (let i = 0; i < logs.length - 1; i++) {
const curr = logs[i].text;
const next = logs[i + 1].text;
const t1 = curr.match(/\d{2}:\d{2}/)?.[0];
const t2 = next.match(/\d{2}:\d{2}/)?.[0];
if (!t1 || !t2) continue;
const hours = (parseTime(t2) - parseTime(t1)) / 60;
if (hours <= 0) continue;
const tags = curr.match(/#\S+/g) ?? [];
for (const tag of tags) {
const parts = tag.slice(1).split("/");
const root = "#" + parts[0];
const child = parts.length > 1 ? parts.slice(1).join("/") : null;
// 向 daily / weekly / monthly 三个桶同时累加
for (const bucket of [
daily[dateKey],
weekly[weekKey],
monthly
]) {
bucket[root] ??= { total: 0, children: {} };
bucket[root].total += hours;
if (child) {
bucket[root].children[child] =
(bucket[root].children[child] ?? 0) + hours;
}
}
}
}
}
/***********************
* 输出工具
***********************/
function renderTable(title, data, firstCol, withSubtotal = false) {
dv.header(3, title);
dv.table(
[firstCol, "标签", "总时长", "明细"],
Object.entries(data)
// 排序(weekly / monthly 都安全)
.sort(([a], [b]) => dv.date(a) - dv.date(b))
.flatMap(([key, roots]) => {
const rows = [];
let subtotal = 0;
const entries = Object.entries(roots);
entries.forEach(([root, info], idx) => {
subtotal += info.total;
const childText = Object.entries(info.children)
.sort((a, b) => b[1] - a[1])
.map(([k, v]) => `${k}: ${v.toFixed(1)}h`) //toFixed中的"1"是小数点后1位
.join("<br>");
rows.push([
idx === 0 ? key : "",
root,
info.total.toFixed(1) + "h",
childText || "<span style='color: gray;'>无</span>"
]);
});
// ⭐ 小计行(只在 weekly / monthly 开启)
if (withSubtotal && entries.length > 0) {
rows.push([
"",
"小计",
`${subtotal.toFixed(1)}h`,
""
]);
}
return rows;
})
);
}
/***********************
* 三张表
***********************/
renderTable("📊 本月统计", { "本月": monthly }, "范围", true);
renderTable("🗓️ 每周统计", weekly, "周", true);
renderTable("⏳ 逐天分布", daily, "日期", false);
} else {
dv.paragraph("❌ 月记文件名中未找到 YYYY-MM");
}此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。