Skip to content
This repository was archived by the owner on Nov 25, 2025. It is now read-only.

Commit ed0e889

Browse files
zsioclaude
andcommitted
refactor: 使用 React Query 优化统计数据管理
- 移除手动 loading 状态管理,使用 React Query 统一处理 - 添加 5 秒自动刷新统计数据功能 - 简化组件代码,提升用户体验 - 移除冗余的加载状态 UI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ede76b4 commit ed0e889

File tree

2 files changed

+32
-60
lines changed

2 files changed

+32
-60
lines changed

src/app/dashboard/_components/statistics/chart.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,13 @@ const getUserColor = (index: number) =>
5656
export interface UserStatisticsChartProps {
5757
data: UserStatisticsData;
5858
onTimeRangeChange?: (timeRange: TimeRange) => void;
59-
loading?: boolean;
6059
}
6160

6261
/**
6362
* 用户统计图表组件
6463
* 展示用户的消费金额和API调用次数
6564
*/
66-
export function UserStatisticsChart({ data, onTimeRangeChange, loading = false }: UserStatisticsChartProps) {
65+
export function UserStatisticsChart({ data, onTimeRangeChange }: UserStatisticsChartProps) {
6766
const [activeChart, setActiveChart] = React.useState<"cost" | "calls">("cost")
6867

6968
// 动态生成图表配置
@@ -247,12 +246,7 @@ export function UserStatisticsChart({ data, onTimeRangeChange, loading = false }
247246
)}
248247
>
249248
<div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 lg:!py-0">
250-
<div className="flex items-center gap-2">
251-
<CardTitle>使用统计</CardTitle>
252-
{loading && (
253-
<div className="h-4 w-4 animate-spin rounded-full border-2 border-orange-600 border-t-transparent" />
254-
)}
255-
</div>
249+
<CardTitle>使用统计</CardTitle>
256250
<CardDescription>
257251
{getTimeRangeDescription()} · {getAggregationLabel()}
258252
</CardDescription>
@@ -262,7 +256,6 @@ export function UserStatisticsChart({ data, onTimeRangeChange, loading = false }
262256
<TimeRangeSelector
263257
value={data.timeRange}
264258
onChange={onTimeRangeChange}
265-
disabled={loading}
266259
className="border-t lg:border-t-0"
267260
/>
268261
)}
Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import * as React from "react";
4+
import { useQuery } from "@tanstack/react-query";
45
import { UserStatisticsChart } from "./chart";
56
import { getUserStatistics } from "@/actions/statistics";
67
import type { TimeRange, UserStatisticsData } from "@/types/statistics";
@@ -11,6 +12,16 @@ interface StatisticsWrapperProps {
1112
initialData?: UserStatisticsData;
1213
}
1314

15+
const STATISTICS_REFRESH_INTERVAL = 5000; // 5秒刷新一次
16+
17+
async function fetchStatistics(timeRange: TimeRange): Promise<UserStatisticsData> {
18+
const result = await getUserStatistics(timeRange);
19+
if (result.ok && result.data) {
20+
return result.data;
21+
}
22+
throw new Error(result.error || '获取统计数据失败');
23+
}
24+
1425
/**
1526
* 统计组件包装器
1627
* 处理时间范围状态管理和数据获取
@@ -19,71 +30,39 @@ export function StatisticsWrapper({ initialData }: StatisticsWrapperProps) {
1930
const [timeRange, setTimeRange] = React.useState<TimeRange>(
2031
initialData?.timeRange ?? DEFAULT_TIME_RANGE
2132
);
22-
const [data, setData] = React.useState<UserStatisticsData | null>(initialData ?? null);
23-
const [loading, setLoading] = React.useState(false);
24-
25-
// 防抖获取统计数据
26-
const fetchStatistics = React.useCallback(async (newTimeRange: TimeRange) => {
27-
if (loading) return; // 防止重复请求
28-
29-
setLoading(true);
30-
try {
31-
const result = await getUserStatistics(newTimeRange);
32-
if (!result.ok) {
33-
toast.error(result.error || '获取统计数据失败');
34-
return;
35-
}
3633

37-
if (!result.data) {
38-
toast.error('统计数据为空');
39-
return;
40-
}
34+
const { data, error } = useQuery<UserStatisticsData, Error>({
35+
queryKey: ["user-statistics", timeRange],
36+
queryFn: () => fetchStatistics(timeRange),
37+
initialData,
38+
refetchInterval: STATISTICS_REFRESH_INTERVAL,
39+
});
4140

42-
setData(result.data);
43-
setTimeRange(newTimeRange);
44-
} catch (error) {
45-
console.error('Failed to fetch statistics:', error);
46-
toast.error('获取统计数据失败');
47-
} finally {
48-
// 添加一个小延迟让用户看到加载状态,避免闪烁
49-
setTimeout(() => setLoading(false), 150);
41+
// 错误提示
42+
React.useEffect(() => {
43+
if (error) {
44+
toast.error(error.message);
5045
}
51-
}, [loading]);
46+
}, [error]);
5247

5348
// 处理时间范围变化
5449
const handleTimeRangeChange = React.useCallback((newTimeRange: TimeRange) => {
55-
if (newTimeRange !== timeRange && !loading) {
56-
fetchStatistics(newTimeRange);
57-
}
58-
}, [timeRange, loading, fetchStatistics]);
50+
setTimeRange(newTimeRange);
51+
}, []);
5952

60-
// 如果没有数据且不在加载中,显示空状态
61-
if (!data && !loading) {
53+
// 如果没有数据,显示空状态
54+
if (!data) {
6255
return (
6356
<div className="text-center py-8 text-muted-foreground">
6457
暂无统计数据
6558
</div>
6659
);
6760
}
6861

69-
// 如果有数据,显示图表
70-
if (data) {
71-
return (
72-
<UserStatisticsChart
73-
data={data}
74-
onTimeRangeChange={handleTimeRangeChange}
75-
loading={loading}
76-
/>
77-
);
78-
}
79-
80-
// 纯加载状态
8162
return (
82-
<div className="text-center py-8">
83-
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
84-
<div className="h-4 w-4 animate-spin rounded-full border-2 border-orange-600 border-t-transparent" />
85-
加载统计数据...
86-
</div>
87-
</div>
63+
<UserStatisticsChart
64+
data={data}
65+
onTimeRangeChange={handleTimeRangeChange}
66+
/>
8867
);
8968
}

0 commit comments

Comments
 (0)