|
9 | 9 | YAxis, |
10 | 10 | Tooltip, |
11 | 11 | } from "recharts"; |
| 12 | +import { useTranslation } from "react-i18next"; |
12 | 13 |
|
13 | 14 | import { type ChartConfig, ChartContainer } from "./chart"; |
14 | 15 | import { SharedChartTooltip } from "./shared-chart-tooltip"; |
@@ -61,101 +62,119 @@ const formatAxisTime = (timestamp: string): string => { |
61 | 62 | }; |
62 | 63 |
|
63 | 64 | // 自定义Tooltip组件 |
64 | | -const DetailedTrafficTooltip = ({ active, payload, label }: any) => ( |
65 | | - <SharedChartTooltip |
66 | | - active={active} |
67 | | - items={payload |
68 | | - ?.map((entry: any) => { |
69 | | - let name = ""; |
70 | | - let color = ""; |
71 | | - |
72 | | - switch (entry.dataKey) { |
73 | | - case "tcpIn": |
74 | | - name = "TCP入站"; |
75 | | - color = "text-blue-600 dark:text-blue-400"; |
76 | | - break; |
77 | | - case "tcpOut": |
78 | | - name = "TCP出站"; |
79 | | - color = "text-green-600 dark:text-green-400"; |
80 | | - break; |
81 | | - case "udpIn": |
82 | | - name = "UDP入站"; |
83 | | - color = "text-purple-600 dark:text-purple-400"; |
84 | | - break; |
85 | | - case "udpOut": |
86 | | - name = "UDP出站"; |
87 | | - color = "text-orange-600 dark:text-orange-400"; |
88 | | - break; |
89 | | - default: |
90 | | - name = entry.name || entry.dataKey; |
91 | | - color = "text-default-600"; |
92 | | - } |
93 | | - |
94 | | - return { |
95 | | - key: entry.dataKey, |
96 | | - name, |
97 | | - value: entry.value, |
98 | | - color, |
99 | | - unit: "traffic" as const, |
100 | | - }; |
101 | | - }) |
102 | | - .filter((item: any) => item.value !== null && item.value !== undefined)} |
103 | | - label={label} |
104 | | - payload={payload} |
105 | | - /> |
106 | | -); |
| 65 | +const DetailedTrafficTooltip = ({ active, payload, label }: any) => { |
| 66 | + const { t } = useTranslation("tunnels"); |
| 67 | + |
| 68 | + return ( |
| 69 | + <SharedChartTooltip |
| 70 | + active={active} |
| 71 | + items={payload |
| 72 | + ?.map((entry: any) => { |
| 73 | + let name = ""; |
| 74 | + let color = ""; |
| 75 | + |
| 76 | + switch (entry.dataKey) { |
| 77 | + case "tcpIn": |
| 78 | + name = t("details.chartTooltips.tcpInbound"); |
| 79 | + color = "text-blue-600 dark:text-blue-400"; |
| 80 | + break; |
| 81 | + case "tcpOut": |
| 82 | + name = t("details.chartTooltips.tcpOutbound"); |
| 83 | + color = "text-green-600 dark:text-green-400"; |
| 84 | + break; |
| 85 | + case "udpIn": |
| 86 | + name = t("details.chartTooltips.udpInbound"); |
| 87 | + color = "text-purple-600 dark:text-purple-400"; |
| 88 | + break; |
| 89 | + case "udpOut": |
| 90 | + name = t("details.chartTooltips.udpOutbound"); |
| 91 | + color = "text-orange-600 dark:text-orange-400"; |
| 92 | + break; |
| 93 | + default: |
| 94 | + name = entry.name || entry.dataKey; |
| 95 | + color = "text-default-600"; |
| 96 | + } |
| 97 | + |
| 98 | + return { |
| 99 | + key: entry.dataKey, |
| 100 | + name, |
| 101 | + value: entry.value, |
| 102 | + color, |
| 103 | + unit: "traffic" as const, |
| 104 | + }; |
| 105 | + }) |
| 106 | + .filter((item: any) => item.value !== null && item.value !== undefined)} |
| 107 | + label={label} |
| 108 | + payload={payload} |
| 109 | + /> |
| 110 | + ); |
| 111 | +}; |
107 | 112 |
|
108 | 113 | // 加载状态组件 |
109 | 114 | const LoadingState: React.FC<{ height: number; className?: string }> = ({ |
110 | 115 | height, |
111 | 116 | className, |
112 | | -}) => ( |
113 | | - <div |
114 | | - className={`flex items-center justify-center ${className}`} |
115 | | - style={height ? { height } : {}} |
116 | | - > |
117 | | - <div className="space-y-1 text-center"> |
118 | | - <div className="flex justify-center"> |
119 | | - <div className="relative w-4 h-4"> |
120 | | - <div className="absolute inset-0 rounded-full border-2 border-default-200 border-t-primary animate-spin" /> |
| 117 | +}) => { |
| 118 | + const { t } = useTranslation("tunnels"); |
| 119 | + |
| 120 | + return ( |
| 121 | + <div |
| 122 | + className={`flex items-center justify-center ${className}`} |
| 123 | + style={height ? { height } : {}} |
| 124 | + > |
| 125 | + <div className="space-y-1 text-center"> |
| 126 | + <div className="flex justify-center"> |
| 127 | + <div className="relative w-4 h-4"> |
| 128 | + <div className="absolute inset-0 rounded-full border-2 border-default-200 border-t-primary animate-spin" /> |
| 129 | + </div> |
121 | 130 | </div> |
| 131 | + <p className="text-default-500 animate-pulse text-xs"> |
| 132 | + {t("details.chartStates.loading")} |
| 133 | + </p> |
122 | 134 | </div> |
123 | | - <p className="text-default-500 animate-pulse text-xs">加载中...</p> |
124 | 135 | </div> |
125 | | - </div> |
126 | | -); |
| 136 | + ); |
| 137 | +}; |
127 | 138 |
|
128 | 139 | // 错误状态组件 |
129 | 140 | const ErrorState: React.FC<{ |
130 | 141 | error: string; |
131 | 142 | height: number; |
132 | 143 | className?: string; |
133 | | -}> = ({ error, height, className }) => ( |
134 | | - <div |
135 | | - className={`flex items-center justify-center ${className}`} |
136 | | - style={height ? { height } : {}} |
137 | | - > |
138 | | - <div className="text-center"> |
139 | | - <p className="text-danger text-xs">加载失败</p> |
140 | | - <p className="text-default-400 text-xs mt-0.5">{error}</p> |
| 144 | +}> = ({ error, height, className }) => { |
| 145 | + const { t } = useTranslation("tunnels"); |
| 146 | + |
| 147 | + return ( |
| 148 | + <div |
| 149 | + className={`flex items-center justify-center ${className}`} |
| 150 | + style={height ? { height } : {}} |
| 151 | + > |
| 152 | + <div className="text-center"> |
| 153 | + <p className="text-danger text-xs">{t("details.chartStates.loadFailed")}</p> |
| 154 | + <p className="text-default-400 text-xs mt-0.5">{error}</p> |
| 155 | + </div> |
141 | 156 | </div> |
142 | | - </div> |
143 | | -); |
| 157 | + ); |
| 158 | +}; |
144 | 159 |
|
145 | 160 | // 空状态组件 |
146 | 161 | const EmptyState: React.FC<{ height: number; className?: string }> = ({ |
147 | 162 | height, |
148 | 163 | className, |
149 | | -}) => ( |
150 | | - <div |
151 | | - className={`flex items-center justify-center ${className}`} |
152 | | - style={height ? { height } : {}} |
153 | | - > |
154 | | - <div className="text-center"> |
155 | | - <p className="text-default-400 text-xs">暂无数据</p> |
| 164 | +}) => { |
| 165 | + const { t } = useTranslation("tunnels"); |
| 166 | + |
| 167 | + return ( |
| 168 | + <div |
| 169 | + className={`flex items-center justify-center ${className}`} |
| 170 | + style={height ? { height } : {}} |
| 171 | + > |
| 172 | + <div className="text-center"> |
| 173 | + <p className="text-default-400 text-xs">{t("details.chartStates.noData")}</p> |
| 174 | + </div> |
156 | 175 | </div> |
157 | | - </div> |
158 | | -); |
| 176 | + ); |
| 177 | +}; |
159 | 178 |
|
160 | 179 | // 时间过滤函数 - 过滤出1小时内的数据 |
161 | 180 | const filterDataTo1Hour = (data: DetailedTrafficDataPoint[]) => { |
@@ -183,21 +202,23 @@ export const DetailedTrafficChart: React.FC<DetailedTrafficChartProps> = ({ |
183 | 202 | className = "", |
184 | 203 | showFullData = false, |
185 | 204 | }) => { |
| 205 | + const { t } = useTranslation("tunnels"); |
| 206 | + |
186 | 207 | const chartConfig = { |
187 | 208 | tcpIn: { |
188 | | - label: "TCP入站", |
| 209 | + label: t("details.chartTooltips.tcpInbound"), |
189 | 210 | color: "hsl(217 91% 60%)", |
190 | 211 | }, |
191 | 212 | tcpOut: { |
192 | | - label: "TCP出站", |
| 213 | + label: t("details.chartTooltips.tcpOutbound"), |
193 | 214 | color: "hsl(142 76% 36%)", |
194 | 215 | }, |
195 | 216 | udpIn: { |
196 | | - label: "UDP入站", |
| 217 | + label: t("details.chartTooltips.udpInbound"), |
197 | 218 | color: "hsl(262 83% 58%)", |
198 | 219 | }, |
199 | 220 | udpOut: { |
200 | | - label: "UDP出站", |
| 221 | + label: t("details.chartTooltips.udpOutbound"), |
201 | 222 | color: "hsl(25 95% 53%)", |
202 | 223 | }, |
203 | 224 | } satisfies ChartConfig; |
|
0 commit comments