feat(效能分析): 人员视图也支持自然天/工作日耗时切换
- capability 接口加 mode 参数,natural 用自然小时、workdays 用工作小时 - 人员视图柱状图随顶部按钮切换,排除休息日的耗时 - 切换按钮自动重新请求 capability 与 flow
This commit is contained in:
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = {};
|
||||
|
||||
@ -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<Ca
|
||||
if (query.spec_models?.length) params.spec_models = query.spec_models.join(",");
|
||||
if (query.since) params.since = query.since;
|
||||
if (query.until) params.until = query.until;
|
||||
if (query.mode) params.mode = query.mode;
|
||||
const { data } = await api.get<CapabilityResponse>("/analytics/capability", { params });
|
||||
return data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user