“开始计时”看起来像最简单的前端功能之一:点一下,开始跑秒;再点一下,提交结果。但企业版 Timesheet Grid 并不是这么做的。它用的是一套专门的 timesheet_timer 服务,把计时状态、字段元数据、项目/任务上下文和单位显示全部绑在一起。
主要参考:
enterprise/timesheet_grid/static/src/services/timesheet_timer_service.jsenterprise/timesheet_grid/static/src/hooks/use_timesheet_timer.jsenterprise/timesheet_grid/static/src/services/timesheet_grid_uom_service.js
一、timer service 的核心价值,是把状态提升为全局服务
timesheetTimerService 不是某个按钮组件内部的局部状态,而是注册到服务层的全局能力。它维护:
- 当前是否有 running timer
- 当前 timer 对应的 timesheet 数据
stepTimer- 相关 header fields 元数据
- 事件总线
bus
这意味着无论计时按钮出现在 grid、kanban、list 还是顶部 header,大家都能消费同一份运行中状态。
二、开始/停止计时并不是纯前端行为
startTimer()、stopTimer() 最终都会调 account.analytic.line 上的方法,比如 action_start_new_timesheet_timer、action_timer_stop。前端只是在包装调用与响应式更新。
这样设计的好处是:计时的真正事实仍在后端,前端只负责把它展示得连续、可操作。
三、header fields 元数据决定了计时表单能不能“懂业务”
服务里专门有 fetchTimerHeaderFields() 与 getTimesheetTimerFieldInfo()。它会动态取 project_id、task_id、name 等字段定义,再附加:
- domain
- placeholder
- context
- onChange
- 是否必填
这让计时输入框不只是“把字段渲染出来”,而是能携带项目必须允许 timesheet、任务默认跟随项目、描述框给出业务语义提示等规则。
四、UoM 同步解决的是“同样一段时间,前端怎么显示”
Timesheet 系统里,用户可能看到小时、小数小时,甚至不同单位格式。如果没有专门的 UoM 服务,计时器、grid 单元格、header 显示就很容易口径不一致。
企业版把这件事抽出来,说明它非常重视“同一份计时事实,在不同组件里以一致单位表达”。
五、最容易踩坑的地方:不要把 running timer 当成页面内临时变量
如果你自己写一个组件,只在组件 state 里维护 timer,很快就会遇到:
- 切视图后状态丢失
- 另一个视图不知道计时已开始
- 停止计时时上下文不一致
- 工时单位显示不统一
官方服务化的做法,本质上就是在避免这些跨视图同步问题。
六、结论
Timesheet Grid 的计时体验之所以像“系统级功能”,不是因为按钮多漂亮,而是因为它把后端计时事实、前端响应式状态、字段元数据和单位显示整合到了同一个服务里。
所以它不是一个按钮,而是一套跨视图共享的前端计时基础设施。
DISCUSSION
评论区