任务管理一直在思源笔记做的,苦于没有很好的办法去追踪每个任务完成的时间。如果可以实现自动记录每个任务的完成时间就可以进行复盘,也可以让自己直观的看到自己的时间花在了哪里。思源笔记推出了数据库以后,结合模板的语法和 Run JS 插件终于实现了这个功能。
功能介绍
这是使用的数据的样式
-
任务时间
这个是指这个任务完成总共的时间,一个任务可能需要在好几天内完成这个是汇总的时间。
-
今日用时
这个是今天这个任务所用的时间
-
开始时间
最近一次的任务开始时间
-
结束时间
最近一次的任务结束时间
-
任务计时
-
开始计时
任务添加到数据中,当我们执行这个任务时点击任务计时的开始按钮,开始计时,显示任务持续的时间,并把开始时间记录到任务快的客制化属性中。按钮变成暂停。
-
暂停计时
点击暂停按钮,暂停计时,按钮变为继续
-
继续计时
点击继续可以继续计时。
-
结束
点击结束的时候,会把任务的时间更新到任务的块的属性中做记录。
-
使用方法
- 导入文件,里面包含有数据库和 JS 代码
UpdateblockJS.sy.zip - 安装 JS 插件,把 JS 代码转换成可调用的方法
-
插件
-
把两个代码块转换成可调用的方法就可以开始使用了
-
功能实现
之前想用模板语法去实现,发现模板中的 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); }); }
-
存在问题和未来展望
-
数据库刷新的问题
如果正在计时,更新了数据库,会导致暂停按钮变为开始按钮。后续如果点击结束任务影响不大。另外一个问题是点击按钮后不能及时刷新,点击开始按钮后,前面的开始时间是没有办法及时更新的。虽然块的属性变了,但是数据库显示没有更新。不知道有没有刷新数据库显示的方法。点击结束按钮后可以点后面的刷新按钮,刷新显示结果。
-
未来想要实现的功能
因为数据都保存在客制化属性中,可以方便的分析。后续想用模板分析每周各个任务的时间和百分比。任务可以分类别,比如分为工作,学习,兴趣。根据类别去分析。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于