1
0

refactor: 前端架构重构 — hook拆分、组件拆分、类型筛选器动态化、Meta API

- 后端新增 GET /api/meta 端点(checkerRegistry.supportedTypes)及 MetaResponse 类型
- 前端 hook 拆分为 use-queries.ts(全局查询+useMeta)和 use-target-detail.ts(Drawer状态)
- TargetDetailDrawer 拆分为 OverviewTab + HistoryTab + history-table-columns + stats.ts
- 类型筛选器由 meta API 动态驱动,删除 target-type-display 静态映射
- 列定义改为工厂函数 createTargetTableColumns(checkerTypes),TargetGroup 新增 columns prop
- 修复 StatusDonut key、StatusBar maxSlots prop、TrendChart 移除 loading prop
- 补充 utils/time、utils/stats、动态列工厂测试,删除旧 mapping 测试
- 同步 delta specs 到主 specs,归档 frontend-architecture-refactor change
This commit is contained in:
2026-05-13 20:55:42 +08:00
parent a62007083d
commit 31aeee6d60
41 changed files with 713 additions and 902 deletions

View File

@@ -0,0 +1,87 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback, useState } from "react";
import type { HistoryResponse, TargetStatus, TrendPoint } from "../../shared/api";
import { subtractHours } from "../utils/time";
import { fetchJson, useTargets } from "./use-queries";
const detailQueryKeys = {
history: (targetId: number, from: string, to: string, page: number) => ["history", targetId, from, to, page] as const,
trend: (targetId: number, from: string, to: string) => ["trend", targetId, from, to] as const,
};
export function useTargetDetail() {
const queryClient = useQueryClient();
const [selectedTargetId, setSelectedTargetId] = useState<null | number>(null);
const [timeFrom, setTimeFrom] = useState("");
const [timeTo, setTimeTo] = useState("");
const [historyPage, setHistoryPage] = useState(1);
const { data: targetsData } = useTargets();
const selectedTarget =
selectedTargetId !== null ? (targetsData?.find((target) => target.id === selectedTargetId) ?? null) : null;
const trend = useQuery({
enabled: selectedTargetId !== null && !!timeFrom && !!timeTo,
queryFn: () =>
fetchJson<TrendPoint[]>(
`/api/targets/${selectedTargetId}/trend?from=${encodeURIComponent(timeFrom)}&to=${encodeURIComponent(timeTo)}`,
),
queryKey:
selectedTargetId !== null && timeFrom && timeTo
? detailQueryKeys.trend(selectedTargetId, timeFrom, timeTo)
: ["trend", "disabled"],
});
const history = useQuery({
enabled: selectedTargetId !== null && !!timeFrom && !!timeTo,
queryFn: () =>
fetchJson<HistoryResponse>(
`/api/targets/${selectedTargetId}/history?from=${encodeURIComponent(timeFrom)}&to=${encodeURIComponent(timeTo)}&page=${historyPage}&pageSize=20`,
),
queryKey:
selectedTargetId !== null && timeFrom && timeTo
? detailQueryKeys.history(selectedTargetId, timeFrom, timeTo, historyPage)
: ["history", "disabled"],
});
const openDrawer = useCallback((target: TargetStatus) => {
setSelectedTargetId(target.id);
const now = new Date();
const from = subtractHours(now, 24);
setTimeFrom(from.toISOString());
setTimeTo(now.toISOString());
setHistoryPage(1);
}, []);
const closeDrawer = useCallback(() => {
setSelectedTargetId(null);
queryClient.removeQueries({ queryKey: ["trend"] });
queryClient.removeQueries({ queryKey: ["history"] });
}, [queryClient]);
const handleTimeChange = useCallback((from: string, to: string) => {
setTimeFrom(from);
setTimeTo(to);
setHistoryPage(1);
}, []);
const handlePageChange = useCallback((page: number) => {
setHistoryPage(page);
}, []);
return {
closeDrawer,
handlePageChange,
handleTimeChange,
historyData: history.data ?? { items: [], page: 1, pageSize: 20, total: 0 },
historyLoading: history.isLoading,
openDrawer,
selectedTarget,
timeFrom,
timeTo,
trendData: trend.data ?? [],
trendLoading: trend.isLoading,
};
}