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

任务管理一直在思源笔记做的,苦于没有很好的办法去追踪每个任务完成的时间。如果可以实现自动记录每个任务的完成时间就可以进行复盘,也可以让自己直观的看到自己的时间花在了哪里。思源笔记推出了数据库以后,结合模板的语法和 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);
      	});
      
      
      }
      

存在问题和未来展望

  • 数据库刷新的问题

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

  • 未来想要实现的功能

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

  • 思源笔记

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

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

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

相关帖子

欢迎来到这里!

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

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

    目前只能 F5 手动刷新文档

  • 其他回帖
  • zyu318

    已经上次了文件,按照说明应该是可以使用了。

  • bpLzUuLylDBLqy5o 1 赞同

    这么搞太折腾了,还是希望思源能支持像 Notion 一样的表达式。

    不想重新做轮子,嵌入个 Lua,提供几个调用数据获取写入的接口就行了。

  • zyu318

    @88250 有没有刷新数据库显示的办法,现在是用 API 更新了属性,数据库显示的内容是不会改变的,要手动修改数据库才能刷新显示。

    1 回复
  • 查看全部回帖