fix: 尝试修复图表无限扩大问题,增强移动端适配性

This commit is contained in:
Montia37
2025-08-16 00:23:55 +08:00
parent c21942f739
commit 33ceb72bcb
4 changed files with 132 additions and 68 deletions

View File

@@ -203,19 +203,59 @@ export const Header = ({
)} )}
{isInstancePage && ( {isInstancePage && (
<> <>
<Button variant="ghost" size="icon" onClick={toggleTheme}> {isMobile ? (
{theme === "dark" ? ( <DropdownMenu>
<Sun className="size-5 text-primary" /> <DropdownMenuTrigger asChild>
) : ( <Button
<Moon className="size-5 text-primary" /> variant="ghost"
)} size="icon"
</Button> className="relative group">
{enableAdminButton && ( <Menu className="size-5 text-primary transition-transform duration-300 group-data-[state=open]:rotate-180" />
<a href="/admin" target="_blank" rel="noopener noreferrer"> <span className="absolute -bottom-1 left-1/2 w-1.5 h-1.5 rounded-full bg-primary transform -translate-x-1/2 scale-0 transition-transform duration-300 group-data-[state=open]:scale-100"></span>
<Button variant="ghost" size="icon"> </Button>
<CircleUserIcon className="size-5 text-primary" /> </DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="animate-in slide-in-from-top-5 duration-300 bg-background/60 backdrop-blur-[10px] border-border/60 rounded-xl">
<DropdownMenuItem onClick={toggleTheme}>
{theme === "dark" ? (
<Sun className="size-4 mr-2 text-primary" />
) : (
<Moon className="size-4 mr-2 text-primary" />
)}
<span>{theme === "dark" ? "浅色模式" : "深色模式"}</span>
</DropdownMenuItem>
{enableAdminButton && (
<DropdownMenuItem asChild>
<a
href="/admin"
target="_blank"
rel="noopener noreferrer"
className="flex items-center">
<CircleUserIcon className="size-4 mr-2 text-primary" />
<span></span>
</a>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
) : (
<>
<Button variant="ghost" size="icon" onClick={toggleTheme}>
{theme === "dark" ? (
<Sun className="size-5 text-primary" />
) : (
<Moon className="size-5 text-primary" />
)}
</Button> </Button>
</a> {enableAdminButton && (
<a href="/admin" target="_blank" rel="noopener noreferrer">
<Button variant="ghost" size="icon">
<CircleUserIcon className="size-5 text-primary" />
</Button>
</a>
)}
</>
)} )}
</> </>
)} )}

View File

@@ -55,12 +55,17 @@ function ChartContainer({
data-slot="chart" data-slot="chart"
data-chart={chartId} data-chart={chartId}
className={cn( className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className className
)} )}
style={{ position: "relative", overflow: "hidden" }}
{...props}> {...props}>
<ChartStyle id={chartId} config={config} /> <ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer> <RechartsPrimitive.ResponsiveContainer
width="100%"
height="100%"
debounce={50}
style={{ position: "relative", overflow: "hidden" }}>
{children} {children}
</RechartsPrimitive.ResponsiveContainer> </RechartsPrimitive.ResponsiveContainer>
</div> </div>

View File

@@ -116,7 +116,7 @@ const Instance = memo(({ node }: InstanceProps) => {
</div> </div>
<div> <div>
<p className="text-muted-foreground text-sm"></p> <p className="text-muted-foreground text-sm"></p>
<p className="flex items-center gap-2"> <div className="flex items-center gap-2">
{node.traffic_limit && isOnline && ( {node.traffic_limit && isOnline && (
<CircleProgress <CircleProgress
value={trafficPercentage} value={trafficPercentage}
@@ -139,7 +139,7 @@ const Instance = memo(({ node }: InstanceProps) => {
)} )}
</p> </p>
</div> </div>
</p> </div>
</div> </div>
<div> <div>
<p className="text-muted-foreground text-sm"></p> <p className="text-muted-foreground text-sm"></p>

View File

@@ -65,8 +65,8 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
); );
// 样式和颜色 // 样式和颜色
const cn = "flex flex-col w-full h-full gap-4 justify-between"; const cn = "flex flex-col w-full overflow-hidden";
const chartMargin = { top: 10, right: 16, bottom: 10, left: 16 }; const chartMargin = { top: 8, right: 16, bottom: 8, left: 16 };
const colors = ["#F38181", "#FCE38A", "#EAFFD0", "#95E1D3"]; const colors = ["#F38181", "#FCE38A", "#EAFFD0", "#95E1D3"];
// 图表配置 // 图表配置
@@ -266,6 +266,12 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
const DataComponent = const DataComponent =
config.type === "area" ? Area : (Line as React.ComponentType<any>); config.type === "area" ? Area : (Line as React.ComponentType<any>);
// 只指定高度,让宽度自适应
const chartProps = {
height: 140, // 更小的高度以确保完全适应容器
style: { overflow: "visible" }, // 通过内联样式解决Safari溢出问题
};
const chartConfig = config.series const chartConfig = config.series
? config.series.reduce((acc: any, series: any) => { ? config.series.reduce((acc: any, series: any) => {
acc[series.dataKey] = { acc[series.dataKey] = {
@@ -282,62 +288,73 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
}; };
return ( return (
<Card className={cn} key={config.id}> <Card className={cn} key={config.id} style={{ height: "220px" }}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 h-[80px]">
<CardTitle className="text-sm font-medium">{config.title}</CardTitle> <CardTitle className="text-sm font-medium">{config.title}</CardTitle>
<span className="text-sm font-bold">{config.value}</span> <div className="text-sm font-bold min-h-[20px] flex items-center">
{config.value}
</div>
</CardHeader> </CardHeader>
<ChartContainer config={chartConfig}> <div
<ChartComponent data={config.data} margin={chartMargin}> className="h-[150px] w-full px-2 pb-2 align-bottom"
<CartesianGrid strokeDasharray="2 4" vertical={false} /> style={{ minHeight: 0 }}>
<XAxis <ChartContainer config={chartConfig} className="h-full w-full">
dataKey="time" <ChartComponent
tickLine={false} data={config.data}
axisLine={false} margin={chartMargin}
tick={{ fontSize: 11 }} {...chartProps}>
tickFormatter={timeFormatter} <CartesianGrid strokeDasharray="2 4" vertical={false} />
interval={0} <XAxis
/> dataKey="time"
<YAxis tickLine={false}
tickLine={false} axisLine={false}
axisLine={false} tick={{ fontSize: 10 }}
domain={config.yAxisDomain} tickFormatter={timeFormatter}
tickFormatter={config.yAxisFormatter} interval={0}
orientation="left" height={20}
type="number" />
tick={{ dx: -10 }} <YAxis
mirror={true} tickLine={false}
/> axisLine={false}
<Tooltip domain={config.yAxisDomain}
cursor={false} tickFormatter={config.yAxisFormatter}
content={(props: any) => ( orientation="left"
<CustomTooltip {...props} chartConfig={config} /> type="number"
)} tick={{ fontSize: 10, dx: -8 }}
/> width={25}
{config.series ? ( mirror={true}
config.series.map((series: any) => ( />
<Tooltip
cursor={false}
content={(props: any) => (
<CustomTooltip {...props} chartConfig={config} />
)}
/>
{config.series ? (
config.series.map((series: any) => (
<DataComponent
key={series.dataKey}
dataKey={series.dataKey}
animationDuration={0}
stroke={series.color}
fill={config.type === "area" ? series.color : undefined}
opacity={0.8}
dot={false}
/>
))
) : (
<DataComponent <DataComponent
key={series.dataKey} dataKey={config.dataKey}
dataKey={series.dataKey}
animationDuration={0} animationDuration={0}
stroke={series.color} stroke={config.color}
fill={config.type === "area" ? series.color : undefined} fill={config.type === "area" ? config.color : undefined}
opacity={0.8} opacity={0.8}
dot={false} dot={false}
/> />
)) )}
) : ( </ChartComponent>
<DataComponent </ChartContainer>
dataKey={config.dataKey} </div>
animationDuration={0}
stroke={config.color}
fill={config.type === "area" ? config.color : undefined}
opacity={0.8}
dot={false}
/>
)}
</ChartComponent>
</ChartContainer>
</Card> </Card>
); );
}; };
@@ -354,7 +371,9 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
<p className="text-red-500">{error}</p> <p className="text-red-500">{error}</p>
</div> </div>
)} )}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div
className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"
style={{ minHeight: 0, overflow: "hidden" }}>
{chartConfigs.map(renderChart)} {chartConfigs.map(renderChart)}
</div> </div>
</div> </div>