写这个工具花费我蛮多时间,写篇总结回顾下过程,巩固下我这次学习的收获🤔。
项目使用
vue3和elm-plus开发。
问题
我是一个番茄工作法的重度使用者,学习前必定一个番茄,在任务明确的情况下,会给任务划分番茄,所以我必须知道当前时间段我所拥有的番茄数量,我最开始采用的是手动计算。
手段计算法实在是麻烦(犯懒了😄),规划两小时的番茄还好,大于两小时规划起麻烦,容易算错还浪费时间。所以产生制作一个辅助时间管理程序的想法。
解决思路
番茄工作法通过将工作时间分割为25分钟的工作块,每个工作块之间休息5分钟,每3个工作块休息长一些时间,帮助人们提高工作效率和集中精力。
从番茄工作法中,可以提取番茄时间,休息,长休息,长休息间隔数量,这四个输入信息。番茄工作法本质上就是:休息干活休息干活,达到大休息间隔数,加长休息时间。
接下来操作就划分一个时间段,什么时候干活,什么时候休息, 假设输入时间段是10:00 - 12:00, 划分的时间如下:
| 时间段 | 时间段划分 | 类型 |
|---|---|---|
| 10:00 - 10:30 | 30 | 第1个番茄 |
| 10:30 - 10:35 | 5 | 第1个休息 |
| 10:35 - 11:05 | 30 | 第2个番茄 |
| 11:05 - 11:10 | 5 | 第2个休息 |
| 11:10 - 11:40 | 30 | 第3个番茄 |
| 11:40 - 11:55 | 15(加长休息时间) | 第3个休息 |
生成了表格之后,我还想到一些其他需求,更具当前时间动态高亮当前时间段(让自己知道处于那个位置)。以及可以更具生成时间段信息,生成统计信息。
实现
核心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为例,划分出时间段如下图
最后一个时间段,明显越界了
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组件重新渲染即可。
对此我想到一下三种方法:
- 在定时器内调用组件的
$foreceUpdate()方法强制更新组件,进而触发tableRowClassName调用(否定掉)。 - 我想通过对渲染的数据进行操作,但是数据已经产生完毕,我想对数据进行一些修改间接触发
tableRowClassName调用。比如给时间段对象增加一个字段,在定时器内触发字段更新,数据变页面变进而触发tableRowClassName调用。(Vue内部有diff算法,也否定掉,发现理论好差需要恶补下🤔) - 在时间段对象中新增一个
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;
};
到此为止就实现表格高亮效果,如下图:
其他的功能都是围绕数据展开很简单,就不介绍了!,有兴趣可以看看我的源码(dev分支)
总结
本次项目我学习到很多东西,写代码多“谋而后定”不要太着急,不然反而会浪费更多时间。只有想清楚才能写出代码,多去验证下自己的思路具备可行性在采取行动。 业务(做什么)也一定要想清楚,毕竟代码是为了实现业务,代码实现了多验证下是否实现**功能。**毕竟现在最熟悉代码,省的以后在回头看代码。