为自己写一个辅助时间管理工具。

1,674 阅读8分钟

写这个工具花费我蛮多时间,写篇总结回顾下过程,巩固下我这次学习的收获🤔。

  1. 程序在线浏览地址
  2. 程序代码仓库(dev1分支)

项目使用vue3elm-plus开发。

问题

我是一个番茄工作法的重度使用者,学习前必定一个番茄,在任务明确的情况下,会给任务划分番茄,所以我必须知道当前时间段我所拥有的番茄数量,我最开始采用的是手动计算。 手动计算 手段计算法实在是麻烦(犯懒了😄),规划两小时的番茄还好,大于两小时规划起麻烦,容易算错还浪费时间。所以产生制作一个辅助时间管理程序的想法。

解决思路

番茄工作法通过将工作时间分割为25分钟的工作块,每个工作块之间休息5分钟,每3个工作块休息长一些时间,帮助人们提高工作效率和集中精力。

从番茄工作法中,可以提取番茄时间休息长休息长休息间隔数量,这四个输入信息。番茄工作法本质上就是:休息干活休息干活,达到大休息间隔数,加长休息时间。 接下来操作就划分一个时间段,什么时候干活,什么时候休息, 假设输入时间段是10:00 - 12:00, 划分的时间如下:

时间段时间段划分类型
10:00 - 10:3030第1个番茄
10:30 - 10:355第1个休息
10:35 - 11:0530第2个番茄
11:05 - 11:105第2个休息
11:10 - 11:4030第3个番茄
11:40 - 11:5515(加长休息时间)第3个休息

生成了表格之后,我还想到一些其他需求,更具当前时间动态高亮当前时间段(让自己知道处于那个位置)。以及可以更具生成时间段信息,生成统计信息image.png

实现

核心useTimeTm函数

我定义了一个名为useTimeTm函数,它负责根据配置信息产生时间段数据。它会返回一个数组,这个数组内部的每个对象表示一个时间段,返回数据结构如下:

[
    {
        "timeBucket": "10:00 - 10:30",
        "timeInterval": 30,   //时间段大小
        "id": 1684634400000,  //同时也是起始时间
        "type": true,	// true 番茄  false 休息
        "count": 1,	//当前是第几个番茄或者休息
        "endTime": 1684636200000,  
        "highlight": false//高亮效果(highlight 辅助其他功能实现)
    },
   ......
]

它接收两个参数timesInfo,configData,timesInfo是数组保存当前时间段的开始时间和结束时间。configData为番茄的配置信息(番茄大小,休息时间.......)。

useTimeTm内部处理

思路:可以看出番茄工作法的规律,就是工作休息工作休息…… 当番茄完成个数等于休息间隔数,将休息时间替换成大休息时。 番茄和休息时间大小不一致,其他的属性都一样,我使用了true表示番茄,false表示休息。每次循环更具当前状态去匹配合适的值, 循环结束后去更新状态。tmCount表示当前番茄的数量它能被休息间隔数整除时,将休息替换成大休息 。具体代码实现


for ( let timestamp = startTimestamp; timestamp < endTimestamp//结束时间搓; ) {

 	let {
 		isWork,
 		tmCount,
 		restCount
 	} = runtimeVars;

 	let timeInterval = getTimeSize( runtimeVars );//获取当前时间段的大小

 	let startTimeObj = dayjs( timestamp ); //开始时间对象

 	let startMinute = startTimeObj.minute();  

 	let endTimeObj = startTimeObj.minute( startMinute + timeInterval ); //截止时间对象

 	// 获取格式化信息
 	let formatStr = startTimeObj.format( FORMAT_RULE ) +
    ' - ' + endTimeObj.format( FORMAT_RULE );
 
 	// 处理结尾时间 
 	if ( endTimeObj.valueOf() > endTimestamp ) {

 		const lastTomObj = segments[ segments.length - 1 ];

 		const minute = dayjs( endTimestamp )
 			.diff( startTimeObj, 'minute' );
 	
 		if ( minute > TOMATO_MIN_TIME ) {
 			if ( isWork ) { //为什么加判断,因为休息我允许他跨界,番茄我不允许!
 				timeInterval = minute

 				const endTime = startTimeObj.add( timeInterval, "minute" )
 					.valueOf();

 				formatStr = startTimeObj.format( FORMAT_RULE ) + ' - ' +
 					dayjs( endTime )
 					.format( FORMAT_RULE );
 			}
 		} else {

 			if ( !lastTomObj.type ) { //false表示休息,
 				// 假设上一个是休息时间,我将其划分成一个整体
 				lastTomObj.endTime = dayjs( lastTomObj.endTime )
 					.add( minute, "minute" )
 					.valueOf();

 				lastTomObj.timeBucket =
 					dayjs( lastTomObj.id )
 					.format( FORMAT_RULE ) +
 					' - ' + dayjs( lastTomObj.endTime )
 					.format( FORMAT_RULE );

 				lastTomObj.timeInterval += minute;
 				console.log( segments );

 				return segments;
 			}
 			
 		}
 	}

 	if ( isWork ) { 
 		tmCount++; //番茄数加一
 	} else {
 		restCount++; //休息数加一
 	}
 	// 保存数据
 	segments.push( {
 		timeBucket: formatStr, //时间段字符串
 		timeInterval, //番茄大小
 		id: timestamp, //id  开始时间
 		type: isWork, //标识是番茄还是休息
 		count: isWork ? tmCount : restCount, //当前是第几个休息还是番茄
 		endTime: endTimeObj.valueOf(), //结束时间
 		highlight: false
 	} );

 	isWork = !isWork; //更新状态

 	runtimeVars = { //保存本次循环数据
 		tmCount,
 		restCount,
 		isWork
 	}
 	// 更新循环条件
 	timestamp = endTimeObj.valueOf();
}

时间段结尾处理

划分出的时间段, 不大可能每个时间段都划分完整,最后一个时间段可能会不足。不足体现在最后一个时间段(番茄或者休息),会跨过可规划的时间(大于截止时间)。 以输入时间段6:00 - 7:15为例,划分出时间段如下图 6:00 - 7:15时间段列表 最后一个时间段,明显越界了7:40大于7:15, 最后的时间段剩余5分钟。代码里面因为小于截止时间,所以进入循环,导致不可规划的时间被划进了。当然实际情况肯定不止这一种🤔。

解决时间段划分越界

当出现越界,最后的时间肯定不足以划分成一个时间段timeRemaining < tomatoTime。我的处理方法是若剩余时间大于10将时间划分成番茄时间,时间小于10分钟划分成休息时间,休息时间跨界不处理。

 if (endTimeObj.valueOf() > endTimestamp) {

      const lastTomObj = segments[segments.length - 1];
    	// 剩余时间
      const minute = dayjs(endTimestamp).diff(startTimeObj, 'minute');
    	
      if (minute > TOMATO_MIN_TIME) { //剩余时间大于10分钟
        if (isWork) { //为什么加判断,因为休息我允许他跨界,番茄我不允许!
          timeInterval = minute 

          const endTime = startTimeObj.add(timeInterval, "minute").valueOf();

          formatStr = startTimeObj.format(FORMAT_RULE) + ' - ' +
            dayjs(endTime).format(FORMAT_RULE);
        }
      } else {

        if (!lastTomObj.type) { //false表示休息,
          // 假设上一个是休息时间,我将其划分成一个整体
          lastTomObj.endTime = dayjs(lastTomObj.endTime).add(minute, "minute").valueOf();

          lastTomObj.timeBucket =
            dayjs(lastTomObj.id).format(FORMAT_RULE) +
            ' - ' + dayjs(lastTomObj.endTime).format(FORMAT_RULE);

          lastTomObj.timeInterval += minute;
          console.log(segments);

          return segments;
        }

      }
    }

假设上一个是休息时间划分完毕之后,剩余时间小于等于10(划分为休息),为避免冗余我将两个休息合并成一个时间段(休息)。 6:00 - 7:15在结尾处理之后的时间段列表,如下图 处理之后

表格动态高亮

有了useTimeTm加工数据,剩下的工作我只需要渲染,渲染完毕之后。我需要动态高亮当前正在进行第几个番茄或者休息,只要在加工数据时记录当前时间段的初始时间截止时间即可,在判断当前时间处于哪一个时间段,就可以实现添加高亮。

<el-table
  :row-class-name="tableRowClassName" //调用回调更具条件添加高亮
  ......
  >
</el-table>

<!-- js代码 -->
const tableRowClassName = function ( {
	row,
	rowIndex
} ) {
	// highLight
	let classStr = "";

	const startUnix = dayjs() //获取当前时间搓
		.valueOf(); 

	const index = props.segments.findIndex(
		( item ) => item.id < startUnix && item.endTime > startUnix
	);//得到高亮id

	if ( index === rowIndex ) { //高亮当前表格
		classStr += "highLight"
	}

	if ( rowIndex % 2 === 0 ) {
		classStr += " warning-row";
	}

	return classStr;
};

上面实现了高亮当前时间段,但是却不是动态的,但是每次刷新页面之后会校准当前高亮时间段,本质上就是重新调用tableRowClassName,也就是说让table组件重新渲染即可。 对此我想到一下三种方法:

  1. 在定时器内调用组件的$foreceUpdate()方法强制更新组件,进而触发tableRowClassName调用(否定掉)。
  2. 我想通过对渲染的数据进行操作,但是数据已经产生完毕,我想对数据进行一些修改间接触发tableRowClassName调用。比如给时间段对象增加一个字段,在定时器内触发字段更新,数据变页面变进而触发tableRowClassName调用。(Vue内部有diff算法,也否定掉,发现理论好差需要恶补下🤔)
  3. 在时间段对象中新增一个highlight字段,为true表示高亮。我动态更新时间段中的lighthigh字段即可(这才是最靠谱的)。

我并不是一下想到这三种方法,而是想到就去做。我并不知道方法行不行,实现不了自己在重新去想。 这样做缺点浪费时间,以后做新功能可以多想几种方法,在里面筛选出合适的去实践,节约点时间(😄)

onMounted(() => {
  setInterval(() => {
    startUnix.value = dayjs().valueOf();
  }, 2000); //
}); 
  
// 计算出当前应该高亮哪行
const saveNewSegments = computed(() => {
  saveSegments.value.forEach((item,index) => {
    if (item.id < startUnix.value && item.endTime > startUnix.value) {
      
      if(index>0){ //更新高亮时,移除掉上一个高亮
      saveSegments.value[index-1].highlight = false;
      }
      
      item.highlight = true;//给当前时间段添加高亮
    }
  });
  return saveSegments.value;
});
  
/* 
1. 用于生成时间段数据,在初始化,以及用户重新选择时间段时调用
*/
watch(info, (info) => {
  const { configData, timeInfo } = info;

  if (Object.entries(configData).length && timeInfo.length) {
    
    // 调用useTimeToTm获取到时间段数据
    saveSegments.value = useTimetoTm(configData, timeInfo);
    ......
  }
});

此时saveNewSegments(时间段数组),内部就已经有数据表示当前应该高亮哪一行。此时将数据渲染即可,渲染表格组件的tableRowClassName内部只需要判断row.highlight为真即可添加高亮

const tableRowClassName = function ({ row, rowIndex }) {
  // highLight
  let classStr = "";
  
  if(row.highlight){ //高亮当前表格
  classStr += "highLight"
  }
  
  if (rowIndex % 2 === 0) {
    classStr += " warning-row";
  }

  return classStr;
};

到此为止就实现表格高亮效果,如下图: image.png 其他的功能都是围绕数据展开很简单,就不介绍了!,有兴趣可以看看我的源码(dev分支)

总结

本次项目我学习到很多东西,写代码多“谋而后定”不要太着急,不然反而会浪费更多时间。只有想清楚才能写出代码,多去验证下自己的思路具备可行性在采取行动业务(做什么)也一定要想清楚,毕竟代码是为了实现业务,代码实现了多验证下是否实现**功能。**毕竟现在最熟悉代码,省的以后在回头看代码。