跳转至

时间管理示例库源码

模板插件好像用不着,好用没做过示例库有点生疏。

第一步:

下载 obsidian 并在插件市场下载下载如下插件:

  • QuickAdd
  • Calendar
  • DataView

并打开核心插件--日记和模板。

第二步:

先配置日记和模板插件,再配置 Calendar 插件,最后配置QuickAdd 插件。

---
ONE: 1️⃣ 工作学习
ONE目标: 570
LearnTime: 0
TWO: 2️⃣ 运动健身
TWO目标: 90
ExerciseTime: 0
THREE: 3️⃣ 休闲娱乐
THREE目标: 120
PlayTime: 0
FOUR: 4️⃣ 生活杂事
FOUR目标: 330
DailyTime: 0
activateTime: "06:00:00"  
currentMode: "DailyTime"
---
```dataview
list without id
"<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: -5px;'>" +
"<span>📖 " + ONE + " <span style='color: var(--text-muted);'>📈 " + round(LearnTime/ONE目标*100) + "%</span></span>" +
"<span style='display: flex;'>" +
    "<span style='width: 100px;'>✅ " + LearnTime + " 分钟</span>" +
    "<span style='width: 100px; color: var(--text-muted);'>⏳ " + round(ONE目标 - LearnTime) + " 分钟</span>" +
"</span>" +
"</div>" +
"<progress value='" + LearnTime + "' max='" + ONE目标 + "' style='width: 100%;'></progress>"
+
"<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: -5px; margin-top: 10px;'>" +
"<span>🏃 " + TWO + " <span style='color: var(--text-muted);'>📈 " + round(ExerciseTime/TWO目标*100) + "%</span></span>" +
"<span style='display: flex;'>" +
    "<span style='width: 100px;'>✅ " + ExerciseTime + " 分钟</span>" +
    "<span style='width: 100px; color: var(--text-muted);'>⏳ " + round(TWO目标 - ExerciseTime) + " 分钟</span>" +
"</span>" +
"</div>" +
"<progress value='" + ExerciseTime + "' max='" + TWO目标 + "' style='width: 100%;'></progress>"
+
"<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: -5px; margin-top: 10px;'>" +
"<span>🎮 " + THREE + " <span style='color: var(--text-muted);'>📈 " + round(PlayTime/THREE目标*100) + "%</span></span>" +
"<span style='display: flex;'>" +
    "<span style='width: 100px;'>✅ " + PlayTime + " 分钟</span>" +
    "<span style='width: 100px; color: var(--text-muted);'>⏳ " + round(THREE目标 - PlayTime) + " 分钟</span>" +
"</span>" +
"</div>" +
"<progress value='" + PlayTime + "' max='" + THREE目标 + "' style='width: 100%;'></progress>"
+
"<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: -5px; margin-top: 10px;'>" +
"<span>🏠 " + FOUR + " <span style='color: var(--text-muted);'>📈 " + round(DailyTime/FOUR目标*100) + "%</span></span>" +
"<span style='display: flex;'>" +
    "<span style='width: 100px;'>✅ " + DailyTime + " 分钟</span>" +
    "<span style='width: 100px; color: var(--text-muted);'>⏳ " + round(FOUR目标 - DailyTime) + " 分钟</span>" +
"</span>" +
"</div>" +
"<progress value='" + DailyTime + "' max='" + FOUR目标 + "' style='width: 100%;'></progress>"
where file.path = this.file.path
```

第三步:

QuickAdd 插件配置过程用到的代码

trackDaily

module.exports = async (params) => {
  const { app } = params;

  const file = app.workspace.getActiveFile();
  if (!file) {
    new Notice("没有检测到当前活动笔记");
    return;
  }

  const LOCK_KEY = "__stateTrackLock__";
  if (window[LOCK_KEY]) {
    new Notice("有状态脚本正在执行,请稍后再点");
    return;
  }
  window[LOCK_KEY] = true;

  const allowedFields = ["LearnTime", "ExerciseTime", "PlayTime", "DailyTime"];
  const modeLabelMap = {
    LearnTime: "学习",
    ExerciseTime: "锻炼",
    PlayTime: "娱乐",
    DailyTime: "日常",
    sleep: "睡眠"
  };
  const nextMode = "DailyTime";
  const nextModeLabel = modeLabelMap[nextMode];
  const logTitle = "状态切换日志:";
  const now = new Date();

  function pad(n) {
    return String(n).padStart(2, "0");
  }

  function formatTime(date) {
    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  function parseActivateTime(timeStr, currentTime) {
    if (typeof timeStr !== "string") return null;
    const parts = timeStr.split(":").map(Number);
    if (parts.length < 2 || parts.some(Number.isNaN)) return null;
    const [h = 0, m = 0, s = 0] = parts;
    const d = new Date(currentTime);
    d.setHours(h, m, s, 0);
    if (d.getTime() > currentTime.getTime()) {
      d.setDate(d.getDate() - 1);
    }
    return d;
  }

  function normalizeNumber(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string" && value.trim() !== "") {
      const n = Number(value);
      return Number.isNaN(n) ? 0 : n;
    }
    return 0;
  }

  function buildLogLine({ nowTime, previousLabel, addedMinutes, creditedLabel, nextLabel }) {
    if (previousLabel === "睡眠") {
      return `- ${nowTime}|切换为${nextLabel},上一状态为睡眠`;
    }
    return `- ${nowTime}|切换为${nextLabel}${creditedLabel}已记录 ${addedMinutes} 分钟~`;
  }

  async function appendLog(line) {
    const content = await app.vault.read(file);
    let newContent = content;

    if (!content.includes(logTitle)) {
      const trimmed = content.replace(/\s*$/, "");
      newContent = `${trimmed}\n\n${logTitle}\n\n${line}\n`;
    } else {
      newContent = content.replace(new RegExp(`${logTitle}\n*`), `${logTitle}\n\n`);
      newContent = newContent.replace(/\s*$/, "");
      newContent += `\n${line}\n`;
    }

    await app.vault.modify(file, newContent);
  }

  let noticeMessage = "";
  let logLine = "";

  try {
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      for (const field of allowedFields) {
        if (frontmatter[field] === undefined) {
          frontmatter[field] = 0;
        }
      }

      if (
        frontmatter.activateTime === undefined ||
        frontmatter.activateTime === null ||
        String(frontmatter.activateTime).trim() === ""
      ) {
        frontmatter.activateTime = "06:00:00";
      }

      if (
        frontmatter.currentMode === undefined ||
        frontmatter.currentMode === null ||
        ![...allowedFields, "sleep"].includes(String(frontmatter.currentMode))
      ) {
        frontmatter.currentMode = "DailyTime";
      }

      const previousMode = String(frontmatter.currentMode);
      const previousLabel = modeLabelMap[previousMode] || previousMode;
      const activateDate = parseActivateTime(String(frontmatter.activateTime).trim(), now);
      if (!activateDate) {
        throw new Error("activateTime 格式错误,应为 HH:mm:ss 或 HH:mm");
      }

      let addedMinutes = Math.floor((now.getTime() - activateDate.getTime()) / 60000);
      if (addedMinutes < 0) addedMinutes = 0;

      let creditedLabel = "无";
      if (allowedFields.includes(previousMode)) {
        const oldValue = normalizeNumber(frontmatter[previousMode]);
        frontmatter[previousMode] = oldValue + addedMinutes;
        creditedLabel = modeLabelMap[previousMode];
        noticeMessage = `已记录 ${creditedLabel} ${addedMinutes} 分钟,开始${nextModeLabel}`;
      } else if (previousMode === "sleep") {
        noticeMessage = `已从睡眠切换到${nextModeLabel}`;
      } else {
        noticeMessage = `检测到异常状态,已切换到${nextModeLabel}`;
      }

      logLine = buildLogLine({
        nowTime: formatTime(now),
        previousLabel,
        addedMinutes,
        creditedLabel,
        nextLabel: nextModeLabel
      });

      frontmatter.currentMode = nextMode;
      frontmatter.activateTime = formatTime(now);
    });

    await appendLog(logLine);
    new Notice(noticeMessage);
  } catch (error) {
    console.error(error);
    new Notice(`记录失败:${error.message}`);
  } finally {
    window[LOCK_KEY] = false;
  }
};

trackExercise

module.exports = async (params) => {
  const { app } = params;

  const file = app.workspace.getActiveFile();
  if (!file) {
    new Notice("没有检测到当前活动笔记");
    return;
  }

  const LOCK_KEY = "__stateTrackLock__";
  if (window[LOCK_KEY]) {
    new Notice("有状态脚本正在执行,请稍后再点");
    return;
  }
  window[LOCK_KEY] = true;

  const allowedFields = ["LearnTime", "ExerciseTime", "PlayTime", "DailyTime"];
  const modeLabelMap = {
    LearnTime: "学习",
    ExerciseTime: "锻炼",
    PlayTime: "娱乐",
    DailyTime: "日常",
    sleep: "睡眠"
  };
  const nextMode = "ExerciseTime";
  const nextModeLabel = modeLabelMap[nextMode];
  const logTitle = "状态切换日志:";
  const now = new Date();

  function pad(n) {
    return String(n).padStart(2, "0");
  }

  function formatTime(date) {
    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  function parseActivateTime(timeStr, currentTime) {
    if (typeof timeStr !== "string") return null;
    const parts = timeStr.split(":").map(Number);
    if (parts.length < 2 || parts.some(Number.isNaN)) return null;
    const [h = 0, m = 0, s = 0] = parts;
    const d = new Date(currentTime);
    d.setHours(h, m, s, 0);
    if (d.getTime() > currentTime.getTime()) {
      d.setDate(d.getDate() - 1);
    }
    return d;
  }

  function normalizeNumber(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string" && value.trim() !== "") {
      const n = Number(value);
      return Number.isNaN(n) ? 0 : n;
    }
    return 0;
  }

  function buildLogLine({ nowTime, previousLabel, addedMinutes, creditedLabel, nextLabel }) {
    if (previousLabel === "睡眠") {
      return `- ${nowTime}|切换为${nextLabel},上一状态为睡眠`;
    }
    return `- ${nowTime}|切换为${nextLabel}${creditedLabel}已记录 ${addedMinutes} 分钟~`;
  }

  async function appendLog(line) {
    const content = await app.vault.read(file);
    let newContent = content;

    if (!content.includes(logTitle)) {
      const trimmed = content.replace(/\s*$/, "");
      newContent = `${trimmed}\n\n${logTitle}\n\n${line}\n`;
    } else {
      newContent = content.replace(new RegExp(`${logTitle}\n*`), `${logTitle}\n\n`);
      newContent = newContent.replace(/\s*$/, "");
      newContent += `\n${line}\n`;
    }

    await app.vault.modify(file, newContent);
  }

  let noticeMessage = "";
  let logLine = "";

  try {
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      for (const field of allowedFields) {
        if (frontmatter[field] === undefined) {
          frontmatter[field] = 0;
        }
      }

      if (
        frontmatter.activateTime === undefined ||
        frontmatter.activateTime === null ||
        String(frontmatter.activateTime).trim() === ""
      ) {
        frontmatter.activateTime = "06:00:00";
      }

      if (
        frontmatter.currentMode === undefined ||
        frontmatter.currentMode === null ||
        ![...allowedFields, "sleep"].includes(String(frontmatter.currentMode))
      ) {
        frontmatter.currentMode = "DailyTime";
      }

      const previousMode = String(frontmatter.currentMode);
      const previousLabel = modeLabelMap[previousMode] || previousMode;
      const activateDate = parseActivateTime(String(frontmatter.activateTime).trim(), now);
      if (!activateDate) {
        throw new Error("activateTime 格式错误,应为 HH:mm:ss 或 HH:mm");
      }

      let addedMinutes = Math.floor((now.getTime() - activateDate.getTime()) / 60000);
      if (addedMinutes < 0) addedMinutes = 0;

      let creditedLabel = "无";
      if (allowedFields.includes(previousMode)) {
        const oldValue = normalizeNumber(frontmatter[previousMode]);
        frontmatter[previousMode] = oldValue + addedMinutes;
        creditedLabel = modeLabelMap[previousMode];
        noticeMessage = `已记录 ${creditedLabel} ${addedMinutes} 分钟,开始${nextModeLabel}`;
      } else if (previousMode === "sleep") {
        noticeMessage = `已从睡眠切换到${nextModeLabel}`;
      } else {
        noticeMessage = `检测到异常状态,已切换到${nextModeLabel}`;
      }

      logLine = buildLogLine({
        nowTime: formatTime(now),
        previousLabel,
        addedMinutes,
        creditedLabel,
        nextLabel: nextModeLabel
      });

      frontmatter.currentMode = nextMode;
      frontmatter.activateTime = formatTime(now);
    });

    await appendLog(logLine);
    new Notice(noticeMessage);
  } catch (error) {
    console.error(error);
    new Notice(`记录失败:${error.message}`);
  } finally {
    window[LOCK_KEY] = false;
  }
};

tracklearn

module.exports = async (params) => {
  const { app } = params;

  const file = app.workspace.getActiveFile();
  if (!file) {
    new Notice("没有检测到当前活动笔记");
    return;
  }

  const LOCK_KEY = "__stateTrackLock__";
  if (window[LOCK_KEY]) {
    new Notice("有状态脚本正在执行,请稍后再点");
    return;
  }
  window[LOCK_KEY] = true;

  const allowedFields = ["LearnTime", "ExerciseTime", "PlayTime", "DailyTime"];
  const modeLabelMap = {
    LearnTime: "学习",
    ExerciseTime: "锻炼",
    PlayTime: "娱乐",
    DailyTime: "日常",
    sleep: "睡眠"
  };
  const nextMode = "LearnTime";
  const nextModeLabel = modeLabelMap[nextMode];
  const logTitle = "状态切换日志:";
  const now = new Date();

  function pad(n) {
    return String(n).padStart(2, "0");
  }

  function formatTime(date) {
    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  function parseActivateTime(timeStr, currentTime) {
    if (typeof timeStr !== "string") return null;
    const parts = timeStr.split(":").map(Number);
    if (parts.length < 2 || parts.some(Number.isNaN)) return null;
    const [h = 0, m = 0, s = 0] = parts;
    const d = new Date(currentTime);
    d.setHours(h, m, s, 0);
    if (d.getTime() > currentTime.getTime()) {
      d.setDate(d.getDate() - 1);
    }
    return d;
  }

  function normalizeNumber(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string" && value.trim() !== "") {
      const n = Number(value);
      return Number.isNaN(n) ? 0 : n;
    }
    return 0;
  }

  function buildLogLine({ nowTime, previousLabel, addedMinutes, creditedLabel, nextLabel }) {
    if (previousLabel === "睡眠") {
      return `- ${nowTime}|切换为${nextLabel},上一状态为睡眠`;
    }
    return `- ${nowTime}|切换为${nextLabel}${creditedLabel}已记录 ${addedMinutes} 分钟~`;
  }

  async function appendLog(line) {
    const content = await app.vault.read(file);
    let newContent = content;

    if (!content.includes(logTitle)) {
      const trimmed = content.replace(/\s*$/, "");
      newContent = `${trimmed}\n\n${logTitle}\n\n${line}\n`;
    } else {
      newContent = content.replace(new RegExp(`${logTitle}\n*`), `${logTitle}\n\n`);
      newContent = newContent.replace(/\s*$/, "");
      newContent += `\n${line}\n`;
    }

    await app.vault.modify(file, newContent);
  }

  let noticeMessage = "";
  let logLine = "";

  try {
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      for (const field of allowedFields) {
        if (frontmatter[field] === undefined) {
          frontmatter[field] = 0;
        }
      }

      if (
        frontmatter.activateTime === undefined ||
        frontmatter.activateTime === null ||
        String(frontmatter.activateTime).trim() === ""
      ) {
        frontmatter.activateTime = "06:00:00";
      }

      if (
        frontmatter.currentMode === undefined ||
        frontmatter.currentMode === null ||
        ![...allowedFields, "sleep"].includes(String(frontmatter.currentMode))
      ) {
        frontmatter.currentMode = "DailyTime";
      }

      const previousMode = String(frontmatter.currentMode);
      const previousLabel = modeLabelMap[previousMode] || previousMode;
      const activateDate = parseActivateTime(String(frontmatter.activateTime).trim(), now);
      if (!activateDate) {
        throw new Error("activateTime 格式错误,应为 HH:mm:ss 或 HH:mm");
      }

      let addedMinutes = Math.floor((now.getTime() - activateDate.getTime()) / 60000);
      if (addedMinutes < 0) addedMinutes = 0;

      let creditedLabel = "无";
      if (allowedFields.includes(previousMode)) {
        const oldValue = normalizeNumber(frontmatter[previousMode]);
        frontmatter[previousMode] = oldValue + addedMinutes;
        creditedLabel = modeLabelMap[previousMode];
        noticeMessage = `已记录 ${creditedLabel} ${addedMinutes} 分钟,开始${nextModeLabel}`;
      } else if (previousMode === "sleep") {
        noticeMessage = `已从睡眠切换到${nextModeLabel}`;
      } else {
        noticeMessage = `检测到异常状态,已切换到${nextModeLabel}`;
      }

      logLine = buildLogLine({
        nowTime: formatTime(now),
        previousLabel,
        addedMinutes,
        creditedLabel,
        nextLabel: nextModeLabel
      });

      frontmatter.currentMode = nextMode;
      frontmatter.activateTime = formatTime(now);
    });

    await appendLog(logLine);
    new Notice(noticeMessage);
  } catch (error) {
    console.error(error);
    new Notice(`记录失败:${error.message}`);
  } finally {
    window[LOCK_KEY] = false;
  }
};

trackPlay

module.exports = async (params) => {
  const { app } = params;

  const file = app.workspace.getActiveFile();
  if (!file) {
    new Notice("没有检测到当前活动笔记");
    return;
  }

  const LOCK_KEY = "__stateTrackLock__";
  if (window[LOCK_KEY]) {
    new Notice("有状态脚本正在执行,请稍后再点");
    return;
  }
  window[LOCK_KEY] = true;

  const allowedFields = ["LearnTime", "ExerciseTime", "PlayTime", "DailyTime"];
  const modeLabelMap = {
    LearnTime: "学习",
    ExerciseTime: "锻炼",
    PlayTime: "娱乐",
    DailyTime: "日常",
    sleep: "睡眠"
  };
  const nextMode = "PlayTime";
  const nextModeLabel = modeLabelMap[nextMode];
  const logTitle = "状态切换日志:";
  const now = new Date();

  function pad(n) {
    return String(n).padStart(2, "0");
  }

  function formatTime(date) {
    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  function parseActivateTime(timeStr, currentTime) {
    if (typeof timeStr !== "string") return null;
    const parts = timeStr.split(":").map(Number);
    if (parts.length < 2 || parts.some(Number.isNaN)) return null;
    const [h = 0, m = 0, s = 0] = parts;
    const d = new Date(currentTime);
    d.setHours(h, m, s, 0);
    if (d.getTime() > currentTime.getTime()) {
      d.setDate(d.getDate() - 1);
    }
    return d;
  }

  function normalizeNumber(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string" && value.trim() !== "") {
      const n = Number(value);
      return Number.isNaN(n) ? 0 : n;
    }
    return 0;
  }

  function buildLogLine({ nowTime, previousLabel, addedMinutes, creditedLabel, nextLabel }) {
    if (previousLabel === "睡眠") {
      return `- ${nowTime}|切换为${nextLabel},上一状态为睡眠`;
    }
    return `- ${nowTime}|切换为${nextLabel}${creditedLabel}已记录 ${addedMinutes} 分钟~`;
  }

  async function appendLog(line) {
    const content = await app.vault.read(file);
    let newContent = content;

    if (!content.includes(logTitle)) {
      const trimmed = content.replace(/\s*$/, "");
      newContent = `${trimmed}\n\n${logTitle}\n\n${line}\n`;
    } else {
      newContent = content.replace(new RegExp(`${logTitle}\n*`), `${logTitle}\n\n`);
      newContent = newContent.replace(/\s*$/, "");
      newContent += `\n${line}\n`;
    }

    await app.vault.modify(file, newContent);
  }

  let noticeMessage = "";
  let logLine = "";

  try {
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      for (const field of allowedFields) {
        if (frontmatter[field] === undefined) {
          frontmatter[field] = 0;
        }
      }

      if (
        frontmatter.activateTime === undefined ||
        frontmatter.activateTime === null ||
        String(frontmatter.activateTime).trim() === ""
      ) {
        frontmatter.activateTime = "06:00:00";
      }

      if (
        frontmatter.currentMode === undefined ||
        frontmatter.currentMode === null ||
        ![...allowedFields, "sleep"].includes(String(frontmatter.currentMode))
      ) {
        frontmatter.currentMode = "DailyTime";
      }

      const previousMode = String(frontmatter.currentMode);
      const previousLabel = modeLabelMap[previousMode] || previousMode;
      const activateDate = parseActivateTime(String(frontmatter.activateTime).trim(), now);
      if (!activateDate) {
        throw new Error("activateTime 格式错误,应为 HH:mm:ss 或 HH:mm");
      }

      let addedMinutes = Math.floor((now.getTime() - activateDate.getTime()) / 60000);
      if (addedMinutes < 0) addedMinutes = 0;

      let creditedLabel = "无";
      if (allowedFields.includes(previousMode)) {
        const oldValue = normalizeNumber(frontmatter[previousMode]);
        frontmatter[previousMode] = oldValue + addedMinutes;
        creditedLabel = modeLabelMap[previousMode];
        noticeMessage = `已记录 ${creditedLabel} ${addedMinutes} 分钟,开始${nextModeLabel}`;
      } else if (previousMode === "sleep") {
        noticeMessage = `已从睡眠切换到${nextModeLabel}`;
      } else {
        noticeMessage = `检测到异常状态,已切换到${nextModeLabel}`;
      }

      logLine = buildLogLine({
        nowTime: formatTime(now),
        previousLabel,
        addedMinutes,
        creditedLabel,
        nextLabel: nextModeLabel
      });

      frontmatter.currentMode = nextMode;
      frontmatter.activateTime = formatTime(now);
    });

    await appendLog(logLine);
    new Notice(noticeMessage);
  } catch (error) {
    console.error(error);
    new Notice(`记录失败:${error.message}`);
  } finally {
    window[LOCK_KEY] = false;
  }
};

trackSleep

module.exports = async (params) => {
  const { app } = params;

  const file = app.workspace.getActiveFile();
  if (!file) {
    new Notice("没有检测到当前活动笔记");
    return;
  }

  const LOCK_KEY = "__stateTrackLock__";
  if (window[LOCK_KEY]) {
    new Notice("有状态脚本正在执行,请稍后再点");
    return;
  }
  window[LOCK_KEY] = true;

  const allowedFields = ["LearnTime", "ExerciseTime", "PlayTime", "DailyTime"];
  const modeLabelMap = {
    LearnTime: "学习",
    ExerciseTime: "锻炼",
    PlayTime: "娱乐",
    DailyTime: "日常",
    sleep: "睡眠"
  };
  const nextMode = "sleep";
  const nextModeLabel = modeLabelMap[nextMode];
  const forceCreditMode = "DailyTime";
  const logTitle = "状态切换日志:";
  const now = new Date();

  function pad(n) {
    return String(n).padStart(2, "0");
  }

  function formatTime(date) {
    return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  function parseActivateTime(timeStr, currentTime) {
    if (typeof timeStr !== "string") return null;
    const parts = timeStr.split(":").map(Number);
    if (parts.length < 2 || parts.some(Number.isNaN)) return null;
    const [h = 0, m = 0, s = 0] = parts;
    const d = new Date(currentTime);
    d.setHours(h, m, s, 0);
    if (d.getTime() > currentTime.getTime()) {
      d.setDate(d.getDate() - 1);
    }
    return d;
  }

  function normalizeNumber(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string" && value.trim() !== "") {
      const n = Number(value);
      return Number.isNaN(n) ? 0 : n;
    }
    return 0;
  }

  function buildLogLine({ nowTime, previousLabel, addedMinutes, creditedLabel, nextLabel }) {
    if (previousLabel === "睡眠") {
      return `- ${nowTime}|切换为${nextLabel},上一状态为睡眠`;
    }
    return `- ${nowTime}|切换为${nextLabel}${creditedLabel}已记录 ${addedMinutes} 分钟~`;
  }

  async function appendLog(line) {
    const content = await app.vault.read(file);
    let newContent = content;

    if (!content.includes(logTitle)) {
      const trimmed = content.replace(/\s*$/, "");
      newContent = `${trimmed}\n\n${logTitle}\n\n${line}\n`;
    } else {
      newContent = content.replace(new RegExp(`${logTitle}\n*`), `${logTitle}\n\n`);
      newContent = newContent.replace(/\s*$/, "");
      newContent += `\n${line}\n`;
    }

    await app.vault.modify(file, newContent);
  }

  let noticeMessage = "";
  let logLine = "";

  try {
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      for (const field of allowedFields) {
        if (frontmatter[field] === undefined) {
          frontmatter[field] = 0;
        }
      }

      if (
        frontmatter.activateTime === undefined ||
        frontmatter.activateTime === null ||
        String(frontmatter.activateTime).trim() === ""
      ) {
        frontmatter.activateTime = "06:00:00";
      }

      if (
        frontmatter.currentMode === undefined ||
        frontmatter.currentMode === null ||
        ![...allowedFields, "sleep"].includes(String(frontmatter.currentMode))
      ) {
        frontmatter.currentMode = "DailyTime";
      }

      const previousMode = String(frontmatter.currentMode);
      const previousLabel = modeLabelMap[previousMode] || previousMode;
      const activateDate = parseActivateTime(String(frontmatter.activateTime).trim(), now);
      if (!activateDate) {
        throw new Error("activateTime 格式错误,应为 HH:mm:ss 或 HH:mm");
      }

      let addedMinutes = Math.floor((now.getTime() - activateDate.getTime()) / 60000);
      if (addedMinutes < 0) addedMinutes = 0;

      let creditedLabel = "无";
      if (allowedFields.includes(previousMode)) {
        const creditMode = forceCreditMode || previousMode;
        const oldValue = normalizeNumber(frontmatter[creditMode]);
        frontmatter[creditMode] = oldValue + addedMinutes;
        creditedLabel = modeLabelMap[creditMode];
        noticeMessage = `已记录 ${creditedLabel} ${addedMinutes} 分钟,开始${nextModeLabel}`;
      } else if (previousMode === "sleep") {
        noticeMessage = `已进入${nextModeLabel}`;
      } else {
        noticeMessage = `检测到异常状态,已切换到${nextModeLabel}`;
      }

      logLine = buildLogLine({
        nowTime: formatTime(now),
        previousLabel,
        addedMinutes,
        creditedLabel,
        nextLabel: nextModeLabel
      });

      frontmatter.currentMode = nextMode;
      frontmatter.activateTime = formatTime(now);
    });

    await appendLog(logLine);
    new Notice(noticeMessage);
  } catch (error) {
    console.error(error);
    new Notice(`记录失败:${error.message}`);
  } finally {
    window[LOCK_KEY] = false;
  }
};