数据库使用新方法 - 任务时间追踪

本贴最后更新于 310 天前,其中的信息可能已经斗转星移

任务管理一直在思源笔记做的,苦于没有很好的办法去追踪每个任务完成的时间。如果可以实现自动记录每个任务的完成时间就可以进行复盘,也可以让自己直观的看到自己的时间花在了哪里。思源笔记推出了数据库以后,结合模板的语法和 Run JS 插件终于实现了这个功能。

功能介绍

这是使用的数据的样式

image

  • 任务时间

    这个是指这个任务完成总共的时间,一个任务可能需要在好几天内完成这个是汇总的时间。

  • 今日用时

    这个是今天这个任务所用的时间

  • 开始时间

    最近一次的任务开始时间

  • 结束时间

    最近一次的任务结束时间

  • 任务计时

    • 开始计时

      任务添加到数据中,当我们执行这个任务时点击任务计时的开始按钮,开始计时,显示任务持续的时间,并把开始时间记录到任务快的客制化属性中。按钮变成暂停。

      image

    • 暂停计时

      点击暂停按钮,暂停计时,按钮变为继续

      image

    • 继续计时

      点击继续可以继续计时。

      image

    • 结束

      点击结束的时候,会把任务的时间更新到任务的块的属性中做记录。

使用方法

  • 导入文件,里面包含有数据库和 JS 代码
    UpdateblockJS.sy.zip
  • 安装 JS 插件,把 JS 代码转换成可调用的方法
    1. 插件image.png

    2. 把两个代码块转换成可调用的方法就可以开始使用了

      image.png

功能实现

之前想用模板语法去实现,发现模板中的 now 不是系统的实时时间而是数据库最后一次刷新的时间,而且数据库一个更新会导致所有的列都更新,导致没有办法保存值。后面只能用点击按钮触发来实现

  • 实现逻辑

    通过点击按钮的时间调用 JS,用 JS 获取系统时间。 调用 API 属性更新对应块的客制化属性。

  • 模板列的 code

    • 开始时间

      .action{$std := index . "custom-starttime"} .action{ if $std} .action{$std1 := substr 11 19 $std } .action{$std1} .action{end}
    • 结束时间

      .action{$etd := index . "custom-endtime"} .action{ if $etd} .action{$etd1 := substr 11 19 $etd } .action{$etd1} .action{end}
    • 任务时间

      .action{$sdu :=index . "custom-durationtime"} .action{if $sdu} .action{$sdu} 分钟 .action{else} 0 分钟 .action{end}
    • 今日用时

      .action{ $cdate := date "20060102" now} .action{ $cdate = list "custom" $cdate | join "-"} .action{ if index . $cdate} .action{ index . $cdate} 分钟 .action{ else} 0 分钟 .action{end}
    • 任务计时

      <div> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Simple Timer with Start/Pause</title> <style> .pretty-div{ display: inline-block; vertical-align: center; } .pretty-button { display: inline-block; vertical-align: center; background-color: #4CAF50; /* 按钮颜色 */ border: none; color: white; /* 文字颜色 */ padding: 5px 12px; /* 内边距 */ text-align: center; /* 文字居中 */ text-decoration: none; display: inline-block; font-size: 12px; /* 字体大小 */ margin: 4px 2px; cursor: pointer; border-radius: 10px; /* 圆角边框 */ transition: background-color 0.3s; /* 动画过渡效果 */ transition: transform 0.3s ease; /* 平滑变大效果 */ } .pretty-button:hover { transform: scale(1.1); /* 按钮变大1.1倍 */ } </style> </head> <body> <div class ="pretty-div" id="timerDisplay.action{.id}">00:00:00</div> <button class = "pretty-button" id="startButton.action{.id}" onclick='event.stopImmediatePropagation(); runJs.plugin.runJsCodeAsync( `plugin.call("timeLog", "start",".action{.id}");`) '>开始</button> <button class = "pretty-button" style="background-color: #e28585;" id="endButton.action{.id}" onclick='event.stopImmediatePropagation(); runJs.plugin.runJsCodeAsync(`plugin.call("timeLog", "end", ".action{.id}");`) '>结束</button> </body> </html> </div>
  • JS code

    需要安装 Run JS 插件。感谢 frostime

    用到的 JS code 一定要定义和我一样的 name

    • 计时的 JS code

      let timerInterval; let startTime = 0; let isRunning; console.log(isRunning) let elapsedTime = 0; let useFunction = args[0]; let blockId = args[1]; console.log (blockId ) let startId = 'startButton' + blockId; console.log (startId) let displayId = 'timerDisplay' + blockId; console.log("start") function startTimer() { const button = document.getElementById(startId); //const pbutton = document.getElementById('pauseButton'); let status = button.textContent; if (status === '开始') { // 如果计时器未运行,开始计时 startTime = Date.now() - elapsedTime; timerInterval = setInterval(updateTimer, 1000); button.textContent= '暂停'; // 更改按钮文本 isRunning = true; plugin.call('updatetime',blockId,'1'); } else if (status === '继续'){ // 点击恢复在原来的基础上重新计时 // 将计时器时间字符串分割为小时、分钟和秒 const [hours, minutes, seconds] = document.getElementById(displayId).textContent.split(':').map(Number); elapsedTime = hours * 3600000 + minutes * 60000 + seconds * 1000; startTime = Date.now() - elapsedTime; timerInterval = setInterval(updateTimer, 1000); button.textContent = '暂停'; //更改按钮文本 button.style.backgroundcolor ="blue";//更改按钮颜色 } else { // 点击暂停按钮清空计时器 let end = setInterval(function() {}, 10000); for (let i = 1; i <= end; i++){ clearInterval(i);} //pbutton.display= 'none'; // 更改按钮文本 //button.dispaly = 'block'; button.textContent = '继续'; // 更改按钮文本 isRunning = false; } } function updateTimer() { const elapsedTime = Date.now() - startTime; updateDisplay(elapsedTime); } function updateDisplay(elapsedTime) { const hours = Math.floor((elapsedTime / 1000 / 60 / 60) % 24); const minutes = Math.floor((elapsedTime / 1000 / 60) % 60); const seconds = Math.floor((elapsedTime / 1000) % 60); document.getElementById(displayId).textContent = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; } function pad(number) { return number < 10 ? '0' + number : number; } // 处理结束计时并发送时间给JS处理的函数 function endTimer() { const buttons = document.getElementById(startId); //if (buttons.textContent === "开始") 数据库如果刷新,会导致按钮变成开始,导致不能结束的问题,去掉判断 //{ //alert('请先点击开始按钮!'); //return; //} let end = setInterval(function() {}, 10000); for (let i = 1; i <= end; i++){ clearInterval(i);} timerInterval = null; // 这里可以添加代码来处理结束计时后的操作,例如发送时间数据 // const timeSpent = document.getElementById(displayId ).textContent; //alert('Timer ended! Time spent: ' + timeSpent); // 重置计时器状态 startTime = 0; const [hours, minutes, seconds] = document.getElementById(displayId).textContent.split(':').map(Number); let timeSpent = hours * 60 + minutes + Math.round(seconds/60); elapsedTime = 0; document.getElementById(startId).textContent = '开始'; plugin.call('updatetime',blockId,'3',timeSpent); } if (useFunction === 'start' || useFunction === 'pause' ) { startTimer(); }; if (useFunction === 'end') { endTimer(); document.getElementById(displayId).textContent ='00:00:00' }
    • 更新属性的 JS code

      // 获取系统时间 const now = new Date(); // 获取当前时间 // 获取年、月、日、小时、分钟、秒,并转换为两位数字格式 const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月份是从0开始的 const day = now.getDate().toString().padStart(2, '0'); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); // API的URL const apiURL = 'api/attr/getBlockAttrs'; //http://127.0.0.1:6806/ // 定义变量方便在API 后赋值 let getduration; let start let duration let durationUpdate; let ddurationUpdate; // 组合成所需的格式 const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; let attrFormat = 'custom-' + `${year}${month}${day}`; // 作为一个自定义属性 YYYYMMDD let updateFiled = args[1] let updateId = args[0].replace(/\s/g, ''); // 去除空格 const params = { "id": updateId }; if (updateFiled === '1') // 1 代表点击的是开始更新开始时间 { runJs.api.setBlockAttrs(`${updateId}`, {"custom-starttime": `${formattedTime}`}); } else if (updateFiled === '3') // 3 代表有duration 的update { console.log ("start"); fetch(apiURL, { method: 'POST', // 假设API需要POST方法 headers: { 'Content-Type': 'application/json' // 设置请求头,表明发送的是JSON数据 }, body: JSON.stringify(params) // 将params对象转换为JSON字符串作为请求体 }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); // 解析JSON数据 }) .then(data => { // 从返回的数据中获取custom-attr1属性 duration = data.data['custom-durationtime']; // 必需用['custom-durationtime'] 不然不能识别,浪费了很大时间才解决 console.log("duration:" + duration ); start = data.data['custom-starttime']; let dduration = data.data[`${attrFormat}`]; // 获取当天的duration console.log(dduration); if (!Boolean(start)) { runJs.siyuan.showMessage("请点击开始,先开始任务!"); return; } console.log ("start to caculate time"); let minutesInit = parseInt (args[2],10); if (!Boolean(duration )) { durationUpdate = minutesInit; console.log ("计算后的duration是 " + durationUpdate ); } else { let minutesToAdd = parseInt(duration, 10); durationUpdate = Math.trunc (minutesInit + minutesToAdd); console.log ("是否在else中跟新 " + durationUpdate); } if (!Boolean(dduration )) { ddurationUpdate = minutesInit; console.log ("计算后的dduration是 " + ddurationUpdate ); } else { let minutesToAdd = parseInt(dduration, 10); ddurationUpdate = Math.trunc (minutesInit + minutesToAdd); console.log ("是否在else 中跟新d " + ddurationUpdate); } console.log ("更新前的dduration " + ddurationUpdate); let attributes = { [attrFormat]: `${ddurationUpdate}`// 这里使用 [attrFormat] 来引用变量作为键 }; runJs.api.setBlockAttrs(`${updateId}`, {"custom-endtime": `${formattedTime}`}); runJs.api.setBlockAttrs(`${updateId}`, {"custom-durationtime": `${durationUpdate}`}); runJs.api.setBlockAttrs(`${updateId}`, attributes ); }) .catch(error => { // 捕获并处理错误 console.error('API调用出错:', error); }); } else // 其他代表更新结束时间 { console.log ("start"); fetch(apiURL, { method: 'POST', // 假设API需要POST方法 headers: { 'Content-Type': 'application/json' // 设置请求头,表明发送的是JSON数据 }, body: JSON.stringify(params) // 将params对象转换为JSON字符串作为请求体 }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); // 解析JSON数据 }) .then(data => { // 从返回的数据中获取custom-attr1属性 duration = data.data['custom-durationtime']; // 必需用['custom-durationtime'] 不然不能识别,浪费了很大时间才解决 console.log("duration:" + duration ); start = data.data['custom-starttime']; console.log(start); if (!Boolean(start)) { runJs.siyuan.showMessage("请点击开始,先开始任务!"); return; } console.log ("start to caculate time"); let start_date = new Date(start); let timeDifference = now.getTime() - start_date.getTime(); let minutesDifference = timeDifference / (1000 * 60); console.log ("time du is " + minutesDifference ); if (!Boolean(duration )) { durationUpdate = Math.trunc(minutesDifference ); console.log ("计算后的duration是 " + durationUpdate ); } else { let minutesToAdd = parseInt(duration, 10); durationUpdate = Math.trunc (minutesDifference + minutesToAdd); console.log ("是否在else中跟新 " + durationUpdate); } console.log ("更新前的duration " + durationUpdate); runJs.api.setBlockAttrs(`${updateId}`, {"custom-endtime": `${formattedTime}`}); runJs.api.setBlockAttrs(`${updateId}`, {"custom-durationtime": `${durationUpdate}`}); }) .catch(error => { // 捕获并处理错误 console.error('API调用出错:', error); }); }

存在问题和未来展望

  • 数据库刷新的问题

    如果正在计时,更新了数据库,会导致暂停按钮变为开始按钮。后续如果点击结束任务影响不大。另外一个问题是点击按钮后不能及时刷新,点击开始按钮后,前面的开始时间是没有办法及时更新的。虽然块的属性变了,但是数据库显示没有更新。不知道有没有刷新数据库显示的方法。点击结束按钮后可以点后面的刷新按钮,刷新显示结果。

  • 未来想要实现的功能

    因为数据都保存在客制化属性中,可以方便的分析。后续想用模板分析每周各个任务的时间和百分比。任务可以分类别,比如分为工作,学习,兴趣。根据类别去分析。

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    25237 引用 • 104082 回帖
1 操作
zyu318 在 2024-06-18 16:09:23 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
请输入回帖内容 ...