feat: 开发任务时间字段重构 + 双口径耗时 + 搜索 + 性能优化

核心变更:
- DevTask 字段重构:startDate/dueDate/estimateHours/actualHours 替换为 expectedStartAt/expectedEndAt/actualStartAt/actualEndAt(含时分),预计/实际工时按工作时段(9:00-12:00 + 13:00-18:00)派生计算
- 状态机自动化:到点自动切开发中、未到可手动开干、超期需填延后原因
- 新建 lib/work-hours.ts:calcWorkHours(工作时段过滤)、formatWorkHours(X h(Y 天))、calcTwoMetrics(日历/人力双口径)
- 新建 lib/dev-task-transitions.ts:状态切换守卫
- 三个 Tab 顶部统计:开发任务(预/日历/人力)、测试用例 / Bug(日历 / 人力);版本概览总人天投入改双行(日历总耗时 / 人力总投入)
- 三个 Tab 加搜索框:300ms debounce + 标题/编号模糊匹配
- 性能优化:requirementIds/categories/requirements/testCases 转 Map/Set 索引、Row 组件 React.memo
- 全局耗时显示统一带天数换算:8h(1天)、11h(1.4天)

新增文件: SearchInput.tsx, useDebouncedValue.ts, work-hours.ts, dev-task-transitions.ts, 设计文档

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-16 17:44:59 +08:00
parent 4ac0dab4c5
commit 1f5bed0c79
25 changed files with 1192 additions and 221 deletions

View File

@@ -0,0 +1,178 @@
# 版本统计「日历耗时 + 人力投入」双口径设计
## Context
当前版本详情下的开发任务/测试用例/Bug 三个 Tab 顶部统计,以及概览页的"总人天投入",都用**单条任务独立累加**计算耗时。问题:
- 任务 A09:0011:002h
- 任务 B10:0012:002h
实际只占用了 09:0012:00 共 3h 工作时段,但当前累加算成 4h。
任务越多重叠越严重,"总耗时"会显著虚高,无法回答"这个版本实际走了几天"。
**目标**:顶部统计同时展示两个口径——
- **日历耗时**:合并区间后的真实工作时段占用(看版本实际走了多久)
- **人力投入**:单条独立累加(看总人力成本,原口径)
## 决策摘要
| 项 | 决策 |
|---|---|
| 算法位置 | `apps/web/lib/work-hours.ts` 新增 `mergeWorkHours` + `calcTwoMetrics` |
| 区间提取 | 各模块dev-task / test-case / bug暴露 `xxxIntervals(items)` 函数 |
| 顶部展示 | 三段式:"预 X / 日历 Y / 人力 Z"(开发任务);"日历 Y / 人力 Z"(测试用例 / Bug |
| 概览 总人天 | 两行展示:日历总耗时 / 人力总投入(含调研/产品/UI/开发/测试/Bug 全程) |
| 个人耗时排名 | 不动(按人汇总天然是人力维度) |
| PlanTab 顶部 | 不动(不在本次范围) |
| 老聚合函数 | 保留(向后兼容,仍然是 manhours |
| Bug 区间 end 优先级 | `closedAt > resolvedAt > 终态 updatedAt > now` |
## 1. 合并区间算法
`apps/web/lib/work-hours.ts` 新增:
```ts
export interface TimeInterval {
start: string;
end: string;
}
// 合并所有重叠区间后,对每段独立调 calcWorkHours 求和
export function mergeWorkHours(intervals: TimeInterval[]): number;
// 双口径汇总
export function calcTwoMetrics(intervals: TimeInterval[]): {
calendarHours: number;
manhours: number;
};
```
算法:
1. 过滤掉非法(`!start || !end`)或 `end <= start` 的区间
2.`start` 升序排序
3. 扫描合并:当前与 prev 重叠(`cur.start <= prev.end`)时合并 `prev.end = max(prev.end, cur.end)`,否则把 prev push 出去
4. 对最终的 N 个不重叠区间,逐个调 `calcWorkHours(start, end)` 求和 → `calendarHours`
5. `manhours` = 原始 intervals 各自 `calcWorkHours` 之后求和(不合并)
精度 0.5h,与现有口径一致。未结束任务以 `now` 作为 end 参与合并。
**验证用例**
- A(911) + B(1012) → 日历 3h人力 4h
- A(911) + B(1315) + C(1014) → 合并后 915 减去午休 1213 = 5h人力 6h
- 跨周末合并:周五 17周一 10 + 周二 911 → 工作时段过滤后两段独立算
## 2. 三个模块的 intervals 提取
**`apps/web/lib/dev-task.ts`** 新增:
```ts
export function devTaskIntervals(tasks: DevTask[], now?: Date): TimeInterval[];
// 取 [actualStartAt, actualEndAt ?? now],过滤无 actualStartAt 的
```
**`apps/web/lib/test-case.ts`** 新增:
```ts
export function testCaseIntervals(cases: TestCase[], now?: Date): TimeInterval[];
// 取 [startedAt, completedAt ?? now],过滤无 startedAt 的
```
**`apps/web/lib/bug.ts`** 新增:
```ts
export function bugIntervals(bugs: Bug[], now?: Date): TimeInterval[];
// 取 [createdAt, closedAt ?? resolvedAt ?? (status 终态时 updatedAt) ?? now]
```
**`apps/web/lib/version-plan.ts`** 新增(仅供概览总人天用):
```ts
export function planIntervals(plans: VersionPlan[], now?: Date): TimeInterval[];
// 取 [actualStartAt, completedAt ?? now],过滤无 actualStartAt 的
```
老聚合函数(`aggregateDevTaskHours / aggregateTestCaseActualHours / aggregateBugActualHours`)保留,仍然是 manhours 累加,向后兼容(项目顶部卡片、与我相关页继续用)。
## 3. 4 个统计点 UI 调整
**(1) `DevTaskTab` 顶部**
```
12任务 · 预 96h12天 · 日历 64h8天 / 人力 84h10.5天)
```
- 染色:人力 > 预计 红、< 预计 绿相等灰
- 日历不染色中性事实
**(2) `TestCaseTab` 顶部**
```
40例 · 通过28 · 失败5 · 通过率85% · 日历 32h4天 / 人力 56h7天
```
**(3) `BugTab` 顶部**
```
Bug 12 个 · 待修复2 · 修复中3 · ... · 解决率75% · 日历 18h / 人力 24h
```
**(4) 概览-总人天投入**line 778当前一行 `总人天投入 96h12人天` 改为两行
```
日历总耗时 104h13天
人力总投入 176h22人天
```
数据源plans + devTasks + testCases + bugs intervals 全部合并后调 `calcTwoMetrics`
## 4. 不动的部分
- 个人耗时排名按人累加本身就是人力维度
- 阶段耗时项目维度卡片已经是日历口径 `calcCalendar` 算最早开始/最晚结束/天数差
- PlanTab调研/产品方案/UI 设计顶部统计
- 项目详情顶部 `HoursStatCard`
- 与我相关页 DevTask 工时显示
## 数据流
```
DevTask + TestCase + Bug + Plan
├── devTaskIntervals(tasks)
├── testCaseIntervals(cases)
├── bugIntervals(bugs)
└── planIntervals(plans)
└── work-hours.calcTwoMetrics(intervals)
├── DevTaskTab 顶部
├── TestCaseTab 顶部
├── BugTab 顶部
└── 概览总人天卡片4 个 intervals 合并)
```
## 实施清单
新增/修改
1. `apps/web/lib/work-hours.ts` `TimeInterval` + `mergeWorkHours` + `calcTwoMetrics`
2. `apps/web/lib/dev-task.ts` `devTaskIntervals`
3. `apps/web/lib/test-case.ts` `testCaseIntervals`
4. `apps/web/lib/bug.ts` `bugIntervals`
5. `apps/web/lib/version-plan.ts` `planIntervals`
6. `apps/web/components/dev-task/DevTaskTab.tsx` 三段式
7. `apps/web/components/test-case/TestCaseTab.tsx` 加日历/人力两段
8. `apps/web/components/bug/BugTab.tsx` 加日历/人力两段
9. `apps/web/app/versions/[id]/page.tsx` 概览"总人天"改两行
## 验证
1. 单条任务 09:0011:00日历=2h人力=2h
2. 两条 A(911) + B(1012)日历=3h人力=4h
3. 三条 A(911) + B(1315) + C(1014)日历=5h去午休人力=6h
4. 跨周末合并周五 17周一 10 与周二 911分两段算工作时段后再相加
5. `pnpm type-check` + `pnpm build` 0 错误
6. 浏览器进版本详情开发/测试/Bug 三个 Tab 顶部三段式正确概览总人天两行正确