feat(前端): 产品卡片双生产总天数 + 效能分析设备总天数按钮切换
- 产品管理卡片同时显示「生产总天数(自然天)」和「生产总天数(工作日)」 - 效能分析页新增「设备生产总天数」统计卡片(设备数/平均/最长),顶部按钮切换 自然天/工作日 - 顺手修复 AnalyticsDashboard Tabs onChange 类型错误
This commit is contained in:
@ -250,7 +250,8 @@ export default function AdminProductsPage() {
|
||||
return <span className={`rounded-md px-1.5 py-0.5 font-bold ${cls}`}>{formatDuration(h)}</span>;
|
||||
})()}
|
||||
</div>
|
||||
<InfoRow icon={CalendarDays} label="生产总天数" value={formatProductionDays(p.created_at)} />
|
||||
<InfoRow icon={CalendarDays} label="生产总天数" value={`${p.production_days ?? formatProductionDays(p.created_at)} 天`} />
|
||||
<InfoRow icon={CalendarDays} label="生产总天数(工作日)" value={p.production_days_workdays != null ? `${p.production_days_workdays} 天` : "—"} />
|
||||
<InfoRow icon={Clock} label="创建时间" value={new Date(p.created_at).toLocaleDateString("zh-CN")} />
|
||||
</div>
|
||||
<div className="border-t border-gray-50 px-4 py-3">
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
* 个人能力图谱:X 轴 = 设备身份证,单机耗时对比,支持点击柱子下钻备注弹窗(含照片)。 */
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { DatePicker, Tabs, Select, Button, Empty, Modal, Timeline, Image } from "antd";
|
||||
import { Users, GitBranch, RefreshCw, Loader2, AlertCircle } from "lucide-react";
|
||||
import { DatePicker, Tabs, Select, Button, Empty, Modal, Timeline, Image, Radio } from "antd";
|
||||
import { Users, GitBranch, RefreshCw, Loader2, AlertCircle, CalendarDays } from "lucide-react";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import type { EChartsCoreOption } from "echarts/core";
|
||||
import "dayjs/locale/zh-cn";
|
||||
@ -88,6 +88,8 @@ export default function AnalyticsDashboard() {
|
||||
const [capabilityLoading, setCapabilityLoading] = useState(false);
|
||||
const [flow, setFlow] = useState<FlowResponse | null>(null);
|
||||
const [flowLoading, setFlowLoading] = useState(false);
|
||||
// 📅 设备生产总天数口径:自然天 / 工作日
|
||||
const [totalDaysMode, setTotalDaysMode] = useState<"natural" | "workdays">("natural");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// ─── 弹窗下钻:设备备注(all=true 表示流转图点击,展示全部) ──
|
||||
@ -673,10 +675,57 @@ export default function AnalyticsDashboard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📅 设备生产总天数(自然天 / 工作日切换) */}
|
||||
<div className="rounded-xl bg-white p-4 shadow-sm">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="flex items-center gap-2 text-sm font-semibold text-gray-700">
|
||||
<CalendarDays className="h-4 w-4 text-emerald-500" /> 📅 设备生产总天数
|
||||
</h3>
|
||||
<Radio.Group
|
||||
value={totalDaysMode}
|
||||
onChange={e => setTotalDaysMode(e.target.value)}
|
||||
size="small"
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
<Radio.Button value="natural">自然天</Radio.Button>
|
||||
<Radio.Button value="workdays">工作日</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
{flowLoading && !flow ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-emerald-500" />
|
||||
</div>
|
||||
) : !flow || flow.devices.length === 0 ? (
|
||||
<div className="py-8 text-center text-sm text-gray-400">暂无设备数据,请选择筛选条件后查询</div>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<div>
|
||||
<p className="text-xs text-gray-400">设备数</p>
|
||||
<p className="text-2xl font-bold text-gray-800">{flow.devices.length}<span className="text-sm font-normal text-gray-400"> 台</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400">{totalDaysMode === "natural" ? "平均生产总天数(自然天)" : "平均生产总天数(工作日)"}</p>
|
||||
<p className="text-2xl font-bold text-emerald-600">
|
||||
{Math.round(flow.devices.reduce((s, d) => s + (totalDaysMode === "natural" ? d.total_days : d.total_workdays), 0) / flow.devices.length)}
|
||||
<span className="text-sm font-normal text-gray-400"> 天</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-400">最长生产周期({totalDaysMode === "natural" ? "自然天" : "工作日"})</p>
|
||||
<p className="text-2xl font-bold text-blue-600">
|
||||
{Math.max(...flow.devices.map(d => totalDaysMode === "natural" ? d.total_days : d.total_workdays))}
|
||||
<span className="text-sm font-normal text-gray-400"> 天</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 主体:两个可视化区块 */}
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
onChange={(k) => setActiveTab(k as "capability" | "flow")}
|
||||
items={[
|
||||
{
|
||||
key: "capability",
|
||||
|
||||
@ -41,6 +41,8 @@ export interface FlowDevice {
|
||||
spec_model: string;
|
||||
lead_time: number; // 设备生命周期总时长(小时)
|
||||
started_at: string; // 设备最早介入时间(T0),MM-DD HH:mm
|
||||
total_days: number; // 设备生产总天数(自然天)
|
||||
total_workdays: number; // 设备生产总天数(工作日)
|
||||
}
|
||||
|
||||
// 时间区间:deviceIndex, startOffset, endOffset, taskName, duration, isMain
|
||||
|
||||
@ -24,6 +24,8 @@ export interface ProductResponse {
|
||||
latest_record_assignee_id: string | null;
|
||||
latest_record_assignee_name: string | null;
|
||||
active_duration_hours: number | null;
|
||||
production_days: number;
|
||||
production_days_workdays: number;
|
||||
}
|
||||
|
||||
/** MOM 物料选项 */
|
||||
|
||||
Reference in New Issue
Block a user