perf: 优化样式和跳转返回页面状态

This commit is contained in:
Montia37
2025-09-05 18:49:31 +08:00
parent b10f40195a
commit e6dc8b1776
6 changed files with 77 additions and 44 deletions

View File

@@ -84,10 +84,9 @@ const Flag = React.memo(({ flag, size }: FlagProps) => {
return (
<Box
as="span"
className={`self-center flex-shrink-0 ${
className={`self-center flex-shrink-0 inline-flex items-center ${
size ? `w-${size} h-${size}` : "w-6 h-6"
}`}
style={{ display: "inline-flex", alignItems: "center" }}
aria-label={altText}>
<img
src={imgSrc}

View File

@@ -62,7 +62,7 @@ export const StatsBar = ({
return (
displayOptions.time && (
<div className="w-full py-1" key="time">
<div className="rt-Flex rt-r-fd-column rt-r-gap-2">
<div className="flex flex-col gap-2">
<label className="text-secondary-foreground text-sm">
</label>
@@ -77,7 +77,7 @@ export const StatsBar = ({
return (
displayOptions.online && (
<div className="w-full py-1" key="online">
<div className="rt-Flex rt-r-fd-column rt-r-gap-2">
<div className="flex flex-col gap-2">
<label className="text-secondary-foreground text-sm">
线
</label>
@@ -94,7 +94,7 @@ export const StatsBar = ({
return (
displayOptions.regions && (
<div className="w-full py-1" key="regions">
<div className="rt-Flex rt-r-fd-column rt-r-gap-2">
<div className="flex flex-col gap-2">
<label className="text-secondary-foreground text-sm">
</label>
@@ -109,7 +109,7 @@ export const StatsBar = ({
return (
displayOptions.traffic && (
<div className="w-full py-1" key="traffic">
<div className="rt-Flex rt-r-fd-column rt-r-gap-2">
<div className="flex flex-col gap-2">
<label className="text-secondary-foreground text-sm">
</label>
@@ -131,7 +131,7 @@ export const StatsBar = ({
return (
displayOptions.speed && (
<div className="w-full py-1" key="speed">
<div className="rt-Flex rt-r-fd-column rt-r-gap-2">
<div className="flex flex-col gap-2">
<label className="text-secondary-foreground text-sm">
</label>

View File

@@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-secondary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex w-full text-sm rounded-md bg-card px-3 py-2 placeholder:text-secondary-foreground disabled:cursor-not-allowed disabled:opacity-50 outline-none focus-visible:ring-0",
className
)}
ref={ref}

View File

@@ -1,4 +1,4 @@
import { useState, useMemo } from "react";
import { useState, useMemo, useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
import { StatsBar } from "@/components/sections/StatsBar";
import { NodeCard } from "@/components/sections/NodeCard";
@@ -15,10 +15,17 @@ interface HomePageProps {
searchTerm: string;
}
const homeStateCache = {
selectedGroup: "所有",
scrollPosition: 0,
};
const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
const { nodes: staticNodes, loading, getGroups } = useNodeData();
const { liveData } = useLiveData();
const [selectedGroup, setSelectedGroup] = useState("所有");
const [selectedGroup, setSelectedGroup] = useState(
homeStateCache.selectedGroup
);
const { enableGroupedBar, enableStatsBar, enableSwap } = useAppConfig();
const [displayOptions, setDisplayOptions] = useState({
time: true,
@@ -79,8 +86,37 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
};
}, [filteredNodes]);
const mainContentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => {
if (mainContentRef.current) {
homeStateCache.scrollPosition = mainContentRef.current.scrollTop;
}
};
const mainContentElement = mainContentRef.current;
mainContentElement?.addEventListener("scroll", handleScroll);
return () => {
mainContentElement?.removeEventListener("scroll", handleScroll);
};
}, []);
useEffect(() => {
if (mainContentRef.current) {
mainContentRef.current.scrollTop = homeStateCache.scrollPosition;
}
}, [loading]);
useEffect(() => {
homeStateCache.selectedGroup = selectedGroup;
}, [selectedGroup]);
return (
<div className="w-[90%] max-w-screen-2xl mx-auto flex-1 flex flex-col pb-10">
<div
ref={mainContentRef}
className="w-[90%] max-w-screen-2xl mx-auto flex-1 flex flex-col pb-10 overflow-y-auto">
{enableStatsBar && (
<StatsBar
displayOptions={displayOptions}
@@ -145,9 +181,7 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
) : (
<div className="text-center py-12">
<p className="text-lg font-bold"></p>
<p className="text-sm text-muted-foreground">
</p>
<p className="text-muted-foreground"></p>
</div>
)}
</div>

View File

@@ -50,28 +50,28 @@ const Instance = memo(({ node }: InstanceProps) => {
</CardHeader>
<CardContent className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-3">
<div className="md:col-span-2">
<p className="text-muted-foreground text-sm">CPU</p>
<p className="text-sm">{`${node.cpu_name} (x${node.cpu_cores})`}</p>
<p className="text-muted-foreground">CPU</p>
<p>{`${node.cpu_name} (x${node.cpu_cores})`}</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">{node.arch}</p>
<p className="text-muted-foreground"></p>
<p>{node.arch}</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">{node.virtualization}</p>
<p className="text-muted-foreground"></p>
<p>{node.virtualization}</p>
</div>
<div>
<p className="text-muted-foreground text-sm">GPU</p>
<p className="text-sm">{node.gpu_name || "N/A"}</p>
<p className="text-muted-foreground">GPU</p>
<p>{node.gpu_name || "N/A"}</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">{node.os}</p>
<p className="text-muted-foreground"></p>
<p>{node.os}</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? `${formatBytes(stats.ram.used)} / ${formatBytes(
node.mem_total
@@ -80,8 +80,8 @@ const Instance = memo(({ node }: InstanceProps) => {
</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? `${formatBytes(stats.swap.used)} / ${formatBytes(
node.swap_total
@@ -90,8 +90,8 @@ const Instance = memo(({ node }: InstanceProps) => {
</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? `${formatBytes(stats.disk.used)} / ${formatBytes(
node.disk_total
@@ -100,12 +100,12 @@ const Instance = memo(({ node }: InstanceProps) => {
</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">{formatUptime(stats?.uptime || 0)}</p>
<p className="text-muted-foreground"></p>
<p>{formatUptime(stats?.uptime || 0)}</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? `${formatBytes(stats.network.up, true)}${formatBytes(
stats.network.down,
@@ -115,7 +115,7 @@ const Instance = memo(({ node }: InstanceProps) => {
</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-muted-foreground"></p>
<div className="flex items-center gap-2">
{node.traffic_limit !== 0 && isOnline && stats && (
<CircleProgress
@@ -127,14 +127,14 @@ const Instance = memo(({ node }: InstanceProps) => {
/>
)}
<div>
<p className="text-sm">
<p>
{stats && isOnline
? `${formatBytes(stats.network.totalUp)}${formatBytes(
stats.network.totalDown
)}`
: "N/A"}
</p>
<p className="text-sm">
<p>
{formatTrafficLimit(
node.traffic_limit,
node.traffic_limit_type
@@ -144,8 +144,8 @@ const Instance = memo(({ node }: InstanceProps) => {
</div>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? `${stats.load.load1.toFixed(2)} | ${stats.load.load5.toFixed(
2
@@ -154,8 +154,8 @@ const Instance = memo(({ node }: InstanceProps) => {
</p>
</div>
<div>
<p className="text-muted-foreground text-sm"></p>
<p className="text-sm">
<p className="text-muted-foreground"></p>
<p>
{stats && isOnline
? new Date(stats.updated_at).toLocaleString()
: "N/A"}

View File

@@ -91,7 +91,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
title: "内存",
type: "area",
value: (
<Flex gap="0" direction="column" align="end" className="text-sm">
<Flex gap="0" direction="column" align="end">
<label>
{liveData?.ram?.used
? `${formatBytes(liveData.ram.used)} / ${formatBytes(
@@ -153,7 +153,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
type: "line",
value: (
<>
<Flex gap="0" align="end" direction="column" className="text-sm">
<Flex gap="0" align="end" direction="column">
<span> {formatBytes(liveData?.network.up || 0)}/s</span>
<span> {formatBytes(liveData?.network.down || 0)}/s</span>
</Flex>
@@ -182,7 +182,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
title: "连接数",
type: "line",
value: (
<Flex gap="0" align="end" direction="column" className="text-sm">
<Flex gap="0" align="end" direction="column">
<span>TCP: {liveData?.connections.tcp}</span>
<span>UDP: {liveData?.connections.udp}</span>
</Flex>