我使用了tasks插件,然后用ai写了自己的日记当中任务完成情况的热力图,但是现在遇到的问题是我的dataviewjs代码并不能及时渲染,必须点一下才会渲染成热力图,我的代码如下
遇到的问题
// ==========================================
// 1. 注入基础 CSS 样式(无需外部 CSS 文件)
// ==========================================
const styleId = "heatmap-custom-styles";
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.innerHTML = `
.heatmap-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 10px; }
.hm-month-container { display: flex; flex-direction: column; gap: 5px; }
.hm-month-title { font-size: 12px; font-weight: bold; text-align: center; }
.hm-calendar { display: grid; grid-template-columns: repeat(7, 14px); gap: 3px; }
.hm-day { width: 14px; height: 14px; border-radius: 3px; cursor: pointer; transition: transform 0.1s; }
.hm-day:hover { transform: scale(1.2); }
.hm-placeholder { background-color: transparent; pointer-events: none; }
`;
document.head.appendChild(style);
}
// ==========================================
// 2. 数据处理逻辑
// ==========================================
const currentYear = new Date().getFullYear();
const colors = {
0: "#f0f0f0", // 更浅的灰色
1: "#FFD1FF", // 稍微加深的浅薰衣草紫
2: "#E6B3FF", // 柔和薰衣草紫
3: "#9933FF", // 基础薰衣草紫
4: "#6600CC", // 深薰衣草紫
5: "#330099" // 浓薰衣草紫
};
const today = new Date();
const todayFormatted = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
const taskCounts = {};
const allTasks = dv.pages().file.tasks.where(t => t.fullyCompleted);
for (let task of allTasks) {
let targetDate = null;
const completionMatch = task.text.match(/✅\s*(\d{4}-\d{2}-\d{2})/);
if (completionMatch) {
targetDate = completionMatch[1];
} else {
const fileDateMatch = task.path.match(/(\d{4}-\d{2}-\d{2})/);
if (fileDateMatch) {
targetDate = fileDateMatch[1];
}
}
if (targetDate) {
taskCounts[targetDate] = (taskCounts[targetDate] || 0) + 1;
}
}
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
// ==========================================
// 3. DOM 渲染与悬浮窗逻辑
// ==========================================
const heatmapContainer = document.createElement("div");
heatmapContainer.className = "heatmap-container";
// 创建全局悬浮窗
const globalTooltip = document.createElement("div");
Object.assign(globalTooltip.style, {
position: "fixed",
display: "none",
backgroundColor: "rgba(0, 0, 0, 0.8)",
color: "#fff",
padding: "6px 10px",
borderRadius: "6px",
fontSize: "12px",
pointerEvents: "none",
zIndex: "99999",
whiteSpace: "nowrap",
boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
});
document.body.appendChild(globalTooltip); // 挂载到 body 彻底脱离容器束缚
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const monthDays = [31, isLeapYear(currentYear) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
months.forEach((month, monthIndex) => {
const monthDiv = document.createElement("div");
monthDiv.className = "hm-month-container";
const monthTitle = document.createElement("div");
monthTitle.className = "hm-month-title";
monthTitle.textContent = month;
monthDiv.appendChild(monthTitle);
const calendarDiv = document.createElement("div");
calendarDiv.className = "hm-calendar";
let firstDayOfWeek = new Date(currentYear, monthIndex, 1).getDay();
firstDayOfWeek = (firstDayOfWeek === 0) ? 6 : firstDayOfWeek - 1;
// 修复点:重新补充该月天数的变量定义
const daysInMonth = monthDays[monthIndex];
for (let i = 0; i < firstDayOfWeek; i++) {
const placeholderDiv = document.createElement("div");
placeholderDiv.className = "hm-day hm-placeholder";
calendarDiv.appendChild(placeholderDiv);
}
for (let day = 1; day <= daysInMonth; day++) {
const date = `${currentYear}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
const count = taskCounts[date] || 0;
const color = colors[Math.min(count, 5)];
const dayDiv = document.createElement("div");
dayDiv.className = "hm-day";
dayDiv.style.backgroundColor = color;
if (date === todayFormatted) {
dayDiv.style.border = "2px solid #ff4d4f"; // 今天用红色边框高亮,更明显
}
// 鼠标事件绑定
dayDiv.addEventListener("mouseenter", () => {
globalTooltip.textContent = `${date}: ${count} tasks`;
globalTooltip.style.display = "block";
});
dayDiv.addEventListener("mousemove", (e) => {
let x = e.clientX + 15;
let y = e.clientY + 15;
// 边缘防遮挡检测
if (x + globalTooltip.offsetWidth > window.innerWidth) {
x = e.clientX - globalTooltip.offsetWidth - 15;
}
if (y + globalTooltip.offsetHeight > window.innerHeight) {
y = e.clientY - globalTooltip.offsetHeight - 15;
}
globalTooltip.style.left = x + "px";
globalTooltip.style.top = y + "px";
});
dayDiv.addEventListener("mouseleave", () => {
globalTooltip.style.display = "none";
});
calendarDiv.appendChild(dayDiv);
}
monthDiv.appendChild(calendarDiv);
heatmapContainer.appendChild(monthDiv);
});
// 清理函数:防止切换页面时产生残留的 tooltip 节点
const currentFile = app.workspace.getActiveFile();
app.workspace.on('file-open', (file) => {
if (file !== currentFile && document.body.contains(globalTooltip)) {
document.body.removeChild(globalTooltip);
}
});
dv.container.appendChild(heatmapContainer);