fix: 调研/产品/UI阶段耗时改为精确时间戳计算

- VersionPlan 新增 actualStartAt 字段
- updatePlan 流转到 in_progress 时自动记录 actualStartAt
- updatePlan 流转到 completed 时自动记录 completedAt(如未设)
- 概览阶段耗时改用 actualStartAt/completedAt 计算(不再用计划排期)
- 个人耗时汇总同步使用实际时间戳

所有阶段(调研/产品/UI/开发/测试)现在统一用精确时间戳算耗时。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-12 16:24:46 +08:00
parent c86e87f95b
commit da1f98968b
3 changed files with 27 additions and 7 deletions

View File

@@ -393,15 +393,24 @@ export default function VersionDetailPage() {
// 调研阶段 // 调研阶段
const researchPlans = versionPlans.filter((p) => p.type === 'research'); const researchPlans = versionPlans.filter((p) => p.type === 'research');
const researchCal = calcCalendar(researchPlans.map((p) => p.startTime), researchPlans.filter((p) => p.status === 'completed').map((p) => p.endTime)); const researchCal = calcCalendar(
researchPlans.filter((p) => p.actualStartAt).map((p) => p.actualStartAt!),
researchPlans.filter((p) => p.completedAt).map((p) => p.completedAt!),
);
// 产品方案阶段 // 产品方案阶段
const productPlans = versionPlans.filter((p) => p.type === 'product'); const productPlans = versionPlans.filter((p) => p.type === 'product');
const productCal = calcCalendar(productPlans.map((p) => p.startTime), productPlans.filter((p) => p.status === 'completed').map((p) => p.endTime)); const productCal = calcCalendar(
productPlans.filter((p) => p.actualStartAt).map((p) => p.actualStartAt!),
productPlans.filter((p) => p.completedAt).map((p) => p.completedAt!),
);
// UI设计阶段 // UI设计阶段
const uiPlans = versionPlans.filter((p) => p.type === 'ui'); const uiPlans = versionPlans.filter((p) => p.type === 'ui');
const uiCal = calcCalendar(uiPlans.map((p) => p.startTime), uiPlans.filter((p) => p.status === 'completed').map((p) => p.endTime)); const uiCal = calcCalendar(
uiPlans.filter((p) => p.actualStartAt).map((p) => p.actualStartAt!),
uiPlans.filter((p) => p.completedAt).map((p) => p.completedAt!),
);
// 开发阶段 // 开发阶段
const devCal = calcCalendar( const devCal = calcCalendar(
@@ -434,9 +443,8 @@ export default function VersionDetailPage() {
// 调研/产品/UI 用 startTime→endTime 计算 // 调研/产品/UI 用 startTime→endTime 计算
versionPlans.forEach((p) => { versionPlans.forEach((p) => {
if (!p.owner || !p.startTime) return; if (!p.owner || !p.actualStartAt) return;
const end = p.status === 'completed' ? p.endTime : undefined; const hours = calcActualHoursByDates(p.actualStartAt, p.completedAt);
const hours = calcActualHoursByDates(p.startTime.slice(0, 10), end?.slice(0, 10));
addHours(p.owner, p.type === 'research' ? 'research' : p.type === 'product' ? 'product' : 'ui', hours); addHours(p.owner, p.type === 'research' ? 'research' : p.type === 'product' ? 'product' : 'ui', hours);
}); });
versionDevTasks.forEach((t) => { versionDevTasks.forEach((t) => {

View File

@@ -24,6 +24,7 @@ export interface VersionPlan {
resultFileData?: string; resultFileData?: string;
remark?: string; remark?: string;
overdueReason?: string; overdueReason?: string;
actualStartAt?: string;
createdAt: string; createdAt: string;
completedAt?: string; completedAt?: string;
addedBy: string; addedBy: string;

View File

@@ -43,7 +43,18 @@ export const useVersionPlanStore = create<VersionPlanState>((set, get) => ({
}, },
updatePlan: (id, data) => { updatePlan: (id, data) => {
const plans = get().plans.map((p) => p.id === id ? { ...p, ...data } : p); const now = new Date().toISOString();
const plans = get().plans.map((p) => {
if (p.id !== id) return p;
const patch = { ...data };
if (patch.status === 'in_progress' && !p.actualStartAt) {
(patch as any).actualStartAt = now;
}
if (patch.status === 'completed' && !p.completedAt) {
(patch as any).completedAt = now;
}
return { ...p, ...patch };
});
set({ plans }); set({ plans });
saveLocal(plans); saveLocal(plans);
}, },