feat(前端): 产品卡片双生产总天数 + 效能分析设备总天数按钮切换

- 产品管理卡片同时显示「生产总天数(自然天)」和「生产总天数(工作日)」
- 效能分析页新增「设备生产总天数」统计卡片(设备数/平均/最长),顶部按钮切换 自然天/工作日
- 顺手修复 AnalyticsDashboard Tabs onChange 类型错误
This commit is contained in:
2026-08-28 15:29:01 +08:00
parent b09187ac6e
commit 7f1858387c
4 changed files with 58 additions and 4 deletions

View File

@ -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">

View File

@ -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",