diff --git a/backend/app/api/v1/endpoints/analytics.py b/backend/app/api/v1/endpoints/analytics.py index 2e06f37..24a917c 100644 --- a/backend/app/api/v1/endpoints/analytics.py +++ b/backend/app/api/v1/endpoints/analytics.py @@ -21,6 +21,7 @@ async def capability_profile( spec_models: str | None = Query(None, description="规格型号,逗号分隔(可选)"), since: str | None = Query(None, description="起始日期 ISO"), until: str | None = Query(None, description="截止日期 ISO"), + mode: str = Query("workdays", description="耗时口径: workdays(工作小时,默认) / natural(自然小时)"), db: AsyncSession = Depends(get_db), ): """个人能力图谱 — X 轴=设备身份证,分组柱状图(单台设备总耗时)。""" @@ -30,7 +31,7 @@ async def capability_profile( specs = [s.strip() for s in spec_models.split(",") if s.strip()] if spec_models else None return await get_capability_profile( db, assignee_ids=ids, spec_models=specs, - since=since_dt, until=until_dt, + since=since_dt, until=until_dt, mode=mode, ) diff --git a/backend/app/services/analytics_service.py b/backend/app/services/analytics_service.py index 46a7832..c7e218f 100644 --- a/backend/app/services/analytics_service.py +++ b/backend/app/services/analytics_service.py @@ -116,10 +116,13 @@ def _to_bj(dt: datetime | None) -> datetime | None: return dt -def _duration_hours(start: datetime | None, end: datetime, holidays: set = None) -> float: - """计算单台耗时(工作小时,排除周末/节假日),无开始时间返回 0。""" +def _duration_hours(start: datetime | None, end: datetime, holidays: set = None, mode: str = "workdays") -> float: + """计算单台耗时(小时),无开始时间返回 0。 + mode=workdays: 排除周末/节假日的工作小时;mode=natural: 自然小时。""" if not start: return 0.0 + if mode == "natural": + return (end - start).total_seconds() / 3600 from app.core.time_utils import working_duration_hours return working_duration_hours(start, end, holidays) @@ -134,6 +137,7 @@ async def get_capability_profile( spec_models: list[str] | None = None, since: datetime | None = None, until: datetime | None = None, + mode: str = "workdays", ) -> CapabilityResponse: """ 个人能力图谱(单机颗粒度):X 轴 = 设备身份证,每个负责人一条柱状 series。 @@ -204,7 +208,7 @@ async def get_capability_profile( "hours": 0.0, "prio": 9, "first_start": None, "last_completed": None, }) - entry["hours"] += _duration_hours(start, end, holidays) + entry["hours"] += _duration_hours(start, end, holidays, mode) if start and (entry["first_start"] is None or start < entry["first_start"]): entry["first_start"] = start completed_dt = _to_bj(row[4]) if row[4] else None diff --git a/frontend/src/pages/admin/AnalyticsDashboard.tsx b/frontend/src/pages/admin/AnalyticsDashboard.tsx index c3c16a5..eeb0c6a 100644 --- a/frontend/src/pages/admin/AnalyticsDashboard.tsx +++ b/frontend/src/pages/admin/AnalyticsDashboard.tsx @@ -120,8 +120,9 @@ export default function AnalyticsDashboard() { q.since = range[0].startOf("day").toISOString(); q.until = range[1].endOf("day").toISOString(); } + q.mode = totalDaysMode; // 自然天 / 工作日,控制人员视图耗时口径 return q; - }, [assigneeIds, specModels, range]); + }, [assigneeIds, specModels, range, totalDaysMode]); const buildFlowQuery = useCallback((): FlowQuery => { const q: FlowQuery = {}; diff --git a/frontend/src/services/analyticsApi.ts b/frontend/src/services/analyticsApi.ts index f4e0150..0f07b28 100644 --- a/frontend/src/services/analyticsApi.ts +++ b/frontend/src/services/analyticsApi.ts @@ -98,6 +98,7 @@ export interface CapabilityQuery { spec_models?: string[]; since?: string; until?: string; + mode?: "natural" | "workdays"; } export interface FlowQuery { @@ -116,6 +117,7 @@ export async function fetchCapabilityProfile(query: CapabilityQuery): Promise("/analytics/capability", { params }); return data; }