feat: Dashboard 刷新频率可配置 — RadioGroup 选择器、动态轮询间隔、手动刷新按钮
- useDashboard hook 改为接受 refetchInterval 动态参数,移除固定 8 秒常量 - Header operations 区域重构为 RadioGroup(手动/10秒/30秒/1分钟/5分钟)+ 倒计时/刷新按钮 - 新增 formatCountdown 工具函数及单元测试 - 新增 .dashboard-refresh-control 和 .dashboard-countdown CSS 类 - 同步更新 DEVELOPMENT.md、README.md、主 specs
This commit is contained in:
@@ -1,30 +1,43 @@
|
||||
import type { SkeletonProps } from "tdesign-react";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, Layout, Menu, Skeleton, Typography } from "tdesign-react";
|
||||
import { RefreshIcon } from "tdesign-icons-react";
|
||||
import { Alert, Button, Layout, Menu, RadioGroup, Skeleton, Typography } from "tdesign-react";
|
||||
|
||||
import { SummaryCards } from "./components/SummaryCards";
|
||||
import { TargetBoard } from "./components/TargetBoard";
|
||||
import { TargetDetailDrawer } from "./components/TargetDetailDrawer";
|
||||
import { DASHBOARD_REFRESH_INTERVAL_MS, useDashboard } from "./hooks/use-queries";
|
||||
import { useDashboard } from "./hooks/use-queries";
|
||||
import { useTargetDetail } from "./hooks/use-target-detail";
|
||||
import { formatCountdown } from "./utils/time";
|
||||
|
||||
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 [now, setNow] = useState(() => new Date());
|
||||
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,
|
||||
} = useDashboard();
|
||||
refetch: refetchDashboard,
|
||||
} = useDashboard(dashboardRefetchInterval);
|
||||
const {
|
||||
closeDrawer,
|
||||
handlePageChange,
|
||||
@@ -38,17 +51,23 @@ export function App() {
|
||||
timeFrom,
|
||||
timeTo,
|
||||
} = useTargetDetail();
|
||||
const isManualRefresh = refreshInterval === 0;
|
||||
const nextRefreshSeconds =
|
||||
dashboardUpdatedAt > 0
|
||||
? Math.max(0, Math.ceil((dashboardUpdatedAt + DASHBOARD_REFRESH_INTERVAL_MS - now.getTime()) / 1000))
|
||||
dashboardUpdatedAt > 0 && !isManualRefresh
|
||||
? Math.max(0, Math.ceil((dashboardUpdatedAt + refreshInterval - now.getTime()) / 1000))
|
||||
: null;
|
||||
const refreshText =
|
||||
dashboardUpdatedAt > 0
|
||||
? dashboardFetching && !dashboardLoading
|
||||
? "刷新中..."
|
||||
: `下一次刷新:${nextRefreshSeconds}秒`
|
||||
: formatCountdown(nextRefreshSeconds ?? 0)
|
||||
: "等待首次刷新";
|
||||
|
||||
const handleIntervalChange = (value: number) => {
|
||||
void refetchDashboard();
|
||||
setRefreshInterval(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => setNow(new Date()), 1000);
|
||||
return () => window.clearInterval(timer);
|
||||
@@ -65,11 +84,30 @@ export function App() {
|
||||
</span>
|
||||
}
|
||||
operations={
|
||||
<span className="dashboard-refresh-status">
|
||||
<Typography.Text className="dashboard-refresh-text" theme="secondary">
|
||||
{refreshText}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
<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">
|
||||
{isManualRefresh ? (
|
||||
<Button
|
||||
aria-label="刷新 Dashboard"
|
||||
disabled={dashboardFetching}
|
||||
icon={<RefreshIcon />}
|
||||
loading={dashboardFetching}
|
||||
onClick={() => void refetchDashboard()}
|
||||
shape="circle"
|
||||
variant="outline"
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text theme="secondary">{refreshText}</Typography.Text>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Header>
|
||||
|
||||
Reference in New Issue
Block a user