- 新建 RefreshCountdown 组件,内部持有 timer,消除 App 每秒重渲染 - TargetBoard 分组逻辑 useMemo 化,避免 targets 引用不变时重复计算 - TargetGroup 加 React.memo,阻断无效渲染 - TrendChart 加 React.memo + chartData useMemo,避免 recharts 不必要重绘 - OverviewTab 统计项去掉 Card 包裹,改用纯 CSS 实现视觉效果 - 同步更新 refresh-control 和 target-detail-drawer spec 性能提升:消除每秒全组件树重渲染,减少 DOM 节点数
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
import type { SkeletonProps } from "tdesign-react";
|
|
|
|
import { useState } from "react";
|
|
import { Alert, Layout, Menu, RadioGroup, Skeleton } from "tdesign-react";
|
|
|
|
import { RefreshCountdown } from "./components/RefreshCountdown";
|
|
import { SummaryCards } from "./components/SummaryCards";
|
|
import { TargetBoard } from "./components/TargetBoard";
|
|
import { TargetDetailDrawer } from "./components/TargetDetailDrawer";
|
|
import { useDashboard } from "./hooks/use-queries";
|
|
import { useTargetDetail } from "./hooks/use-target-detail";
|
|
|
|
const { Content, Header } = Layout;
|
|
const DEFAULT_REFRESH_INTERVAL_MS = 30000;
|
|
const DASHBOARD_SKELETON_ROW_COL: SkeletonProps["rowCol"] = [
|
|
[{ height: "112px", type: "rect", width: "100%" }],
|
|
[{ height: "56px", type: "rect", width: "100%" }],
|
|
[{ height: "320px", type: "rect", width: "100%" }],
|
|
];
|
|
const REFRESH_OPTIONS = [
|
|
{ label: "手动", value: 0 },
|
|
{ label: "10秒", value: 10000 },
|
|
{ label: "30秒", value: 30000 },
|
|
{ label: "1分钟", value: 60000 },
|
|
{ label: "5分钟", value: 300000 },
|
|
] as const;
|
|
|
|
export function App() {
|
|
const [refreshInterval, setRefreshInterval] = useState(DEFAULT_REFRESH_INTERVAL_MS);
|
|
const dashboardRefetchInterval = refreshInterval === 0 ? false : refreshInterval;
|
|
const {
|
|
data: dashboard,
|
|
dataUpdatedAt: dashboardUpdatedAt,
|
|
error: dashboardError,
|
|
isFetching: dashboardFetching,
|
|
isLoading: dashboardLoading,
|
|
refetch: refetchDashboard,
|
|
} = useDashboard(dashboardRefetchInterval);
|
|
const {
|
|
activeTab,
|
|
closeDrawer,
|
|
handlePageChange,
|
|
handleTabChange,
|
|
handleTimeChange,
|
|
historyData,
|
|
historyLoading,
|
|
metricsData,
|
|
metricsLoading,
|
|
openDrawer,
|
|
selectedTarget,
|
|
timeFrom,
|
|
timeTo,
|
|
} = useTargetDetail();
|
|
const isManualRefresh = refreshInterval === 0;
|
|
|
|
const handleIntervalChange = (value: number) => {
|
|
void refetchDashboard();
|
|
setRefreshInterval(value);
|
|
};
|
|
|
|
return (
|
|
<Layout className="dashboard">
|
|
<Header>
|
|
<Menu.HeadMenu
|
|
logo={
|
|
<span className="dashboard-brand">
|
|
<span className="dashboard-logo">DiAL</span>
|
|
<span className="dashboard-subtitle">统一拨测平台</span>
|
|
</span>
|
|
}
|
|
operations={
|
|
<div className="dashboard-refresh-control">
|
|
<RadioGroup
|
|
onChange={handleIntervalChange}
|
|
options={REFRESH_OPTIONS.map((option) => ({ label: option.label, value: option.value }))}
|
|
theme="button"
|
|
value={refreshInterval}
|
|
variant="default-filled"
|
|
/>
|
|
<span className="dashboard-countdown">
|
|
<RefreshCountdown
|
|
dashboardUpdatedAt={dashboardUpdatedAt}
|
|
isFetching={dashboardFetching && !dashboardLoading}
|
|
isManualRefresh={isManualRefresh}
|
|
onRefresh={() => void refetchDashboard()}
|
|
refreshInterval={refreshInterval}
|
|
/>
|
|
</span>
|
|
</div>
|
|
}
|
|
/>
|
|
</Header>
|
|
<Content>
|
|
<div className="dashboard-content">
|
|
{dashboardError && <Alert closeBtn message={`请求失败: ${dashboardError.message}`} theme="error" />}
|
|
|
|
{dashboardLoading ? (
|
|
<Skeleton animation="gradient" rowCol={DASHBOARD_SKELETON_ROW_COL} />
|
|
) : (
|
|
<>
|
|
<SummaryCards summary={dashboard?.summary ?? null} />
|
|
<TargetBoard onTargetClick={openDrawer} targets={dashboard?.targets ?? []} />
|
|
</>
|
|
)}
|
|
</div>
|
|
</Content>
|
|
<TargetDetailDrawer
|
|
activeTab={activeTab}
|
|
historyData={historyData}
|
|
historyLoading={historyLoading}
|
|
metricsData={metricsData}
|
|
metricsLoading={metricsLoading}
|
|
onClose={closeDrawer}
|
|
onPageChange={handlePageChange}
|
|
onTabChange={handleTabChange}
|
|
onTimeChange={handleTimeChange}
|
|
target={selectedTarget}
|
|
timeFrom={timeFrom}
|
|
timeTo={timeTo}
|
|
/>
|
|
</Layout>
|
|
);
|
|
}
|