mirror of
https://github.com/fankes/komari-theme-purcarte.git
synced 2025-12-14 05:21:19 +08:00
perf: 优化样式和跳转返回页面状态
This commit is contained in:
@@ -84,10 +84,9 @@ const Flag = React.memo(({ flag, size }: FlagProps) => {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="span"
|
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"
|
size ? `w-${size} h-${size}` : "w-6 h-6"
|
||||||
}`}
|
}`}
|
||||||
style={{ display: "inline-flex", alignItems: "center" }}
|
|
||||||
aria-label={altText}>
|
aria-label={altText}>
|
||||||
<img
|
<img
|
||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const StatsBar = ({
|
|||||||
return (
|
return (
|
||||||
displayOptions.time && (
|
displayOptions.time && (
|
||||||
<div className="w-full py-1" key="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 className="text-secondary-foreground text-sm">
|
||||||
当前时间
|
当前时间
|
||||||
</label>
|
</label>
|
||||||
@@ -77,7 +77,7 @@ export const StatsBar = ({
|
|||||||
return (
|
return (
|
||||||
displayOptions.online && (
|
displayOptions.online && (
|
||||||
<div className="w-full py-1" key="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 className="text-secondary-foreground text-sm">
|
||||||
当前在线
|
当前在线
|
||||||
</label>
|
</label>
|
||||||
@@ -94,7 +94,7 @@ export const StatsBar = ({
|
|||||||
return (
|
return (
|
||||||
displayOptions.regions && (
|
displayOptions.regions && (
|
||||||
<div className="w-full py-1" key="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 className="text-secondary-foreground text-sm">
|
||||||
点亮地区
|
点亮地区
|
||||||
</label>
|
</label>
|
||||||
@@ -109,7 +109,7 @@ export const StatsBar = ({
|
|||||||
return (
|
return (
|
||||||
displayOptions.traffic && (
|
displayOptions.traffic && (
|
||||||
<div className="w-full py-1" key="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 className="text-secondary-foreground text-sm">
|
||||||
流量概览
|
流量概览
|
||||||
</label>
|
</label>
|
||||||
@@ -131,7 +131,7 @@ export const StatsBar = ({
|
|||||||
return (
|
return (
|
||||||
displayOptions.speed && (
|
displayOptions.speed && (
|
||||||
<div className="w-full py-1" key="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 className="text-secondary-foreground text-sm">
|
||||||
网络速率
|
网络速率
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo, useEffect, useRef } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { StatsBar } from "@/components/sections/StatsBar";
|
import { StatsBar } from "@/components/sections/StatsBar";
|
||||||
import { NodeCard } from "@/components/sections/NodeCard";
|
import { NodeCard } from "@/components/sections/NodeCard";
|
||||||
@@ -15,10 +15,17 @@ interface HomePageProps {
|
|||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const homeStateCache = {
|
||||||
|
selectedGroup: "所有",
|
||||||
|
scrollPosition: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
|
const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
|
||||||
const { nodes: staticNodes, loading, getGroups } = useNodeData();
|
const { nodes: staticNodes, loading, getGroups } = useNodeData();
|
||||||
const { liveData } = useLiveData();
|
const { liveData } = useLiveData();
|
||||||
const [selectedGroup, setSelectedGroup] = useState("所有");
|
const [selectedGroup, setSelectedGroup] = useState(
|
||||||
|
homeStateCache.selectedGroup
|
||||||
|
);
|
||||||
const { enableGroupedBar, enableStatsBar, enableSwap } = useAppConfig();
|
const { enableGroupedBar, enableStatsBar, enableSwap } = useAppConfig();
|
||||||
const [displayOptions, setDisplayOptions] = useState({
|
const [displayOptions, setDisplayOptions] = useState({
|
||||||
time: true,
|
time: true,
|
||||||
@@ -79,8 +86,37 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
|
|||||||
};
|
};
|
||||||
}, [filteredNodes]);
|
}, [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 (
|
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 && (
|
{enableStatsBar && (
|
||||||
<StatsBar
|
<StatsBar
|
||||||
displayOptions={displayOptions}
|
displayOptions={displayOptions}
|
||||||
@@ -145,9 +181,7 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-lg font-bold">没有结果</p>
|
<p className="text-lg font-bold">没有结果</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-muted-foreground">请尝试更改筛选条件</p>
|
||||||
请尝试更改筛选条件
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,28 +50,28 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
<CardContent className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<p className="text-muted-foreground text-sm">CPU</p>
|
<p className="text-muted-foreground">CPU</p>
|
||||||
<p className="text-sm">{`${node.cpu_name} (x${node.cpu_cores})`}</p>
|
<p>{`${node.cpu_name} (x${node.cpu_cores})`}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">架构</p>
|
<p className="text-muted-foreground">架构</p>
|
||||||
<p className="text-sm">{node.arch}</p>
|
<p>{node.arch}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">虚拟化</p>
|
<p className="text-muted-foreground">虚拟化</p>
|
||||||
<p className="text-sm">{node.virtualization}</p>
|
<p>{node.virtualization}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">GPU</p>
|
<p className="text-muted-foreground">GPU</p>
|
||||||
<p className="text-sm">{node.gpu_name || "N/A"}</p>
|
<p>{node.gpu_name || "N/A"}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">操作系统</p>
|
<p className="text-muted-foreground">操作系统</p>
|
||||||
<p className="text-sm">{node.os}</p>
|
<p>{node.os}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">内存</p>
|
<p className="text-muted-foreground">内存</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `${formatBytes(stats.ram.used)} / ${formatBytes(
|
? `${formatBytes(stats.ram.used)} / ${formatBytes(
|
||||||
node.mem_total
|
node.mem_total
|
||||||
@@ -80,8 +80,8 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">交换</p>
|
<p className="text-muted-foreground">交换</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `${formatBytes(stats.swap.used)} / ${formatBytes(
|
? `${formatBytes(stats.swap.used)} / ${formatBytes(
|
||||||
node.swap_total
|
node.swap_total
|
||||||
@@ -90,8 +90,8 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">磁盘</p>
|
<p className="text-muted-foreground">磁盘</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `${formatBytes(stats.disk.used)} / ${formatBytes(
|
? `${formatBytes(stats.disk.used)} / ${formatBytes(
|
||||||
node.disk_total
|
node.disk_total
|
||||||
@@ -100,12 +100,12 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">运行时间</p>
|
<p className="text-muted-foreground">运行时间</p>
|
||||||
<p className="text-sm">{formatUptime(stats?.uptime || 0)}</p>
|
<p>{formatUptime(stats?.uptime || 0)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">实时网络</p>
|
<p className="text-muted-foreground">实时网络</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `↑ ${formatBytes(stats.network.up, true)} ↓ ${formatBytes(
|
? `↑ ${formatBytes(stats.network.up, true)} ↓ ${formatBytes(
|
||||||
stats.network.down,
|
stats.network.down,
|
||||||
@@ -115,7 +115,7 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">总流量</p>
|
<p className="text-muted-foreground">总流量</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{node.traffic_limit !== 0 && isOnline && stats && (
|
{node.traffic_limit !== 0 && isOnline && stats && (
|
||||||
<CircleProgress
|
<CircleProgress
|
||||||
@@ -127,14 +127,14 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `↑ ${formatBytes(stats.network.totalUp)} ↓ ${formatBytes(
|
? `↑ ${formatBytes(stats.network.totalUp)} ↓ ${formatBytes(
|
||||||
stats.network.totalDown
|
stats.network.totalDown
|
||||||
)}`
|
)}`
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{formatTrafficLimit(
|
{formatTrafficLimit(
|
||||||
node.traffic_limit,
|
node.traffic_limit,
|
||||||
node.traffic_limit_type
|
node.traffic_limit_type
|
||||||
@@ -144,8 +144,8 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">负载</p>
|
<p className="text-muted-foreground">负载</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? `${stats.load.load1.toFixed(2)} | ${stats.load.load5.toFixed(
|
? `${stats.load.load1.toFixed(2)} | ${stats.load.load5.toFixed(
|
||||||
2
|
2
|
||||||
@@ -154,8 +154,8 @@ const Instance = memo(({ node }: InstanceProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">最后上报</p>
|
<p className="text-muted-foreground">最后上报</p>
|
||||||
<p className="text-sm">
|
<p>
|
||||||
{stats && isOnline
|
{stats && isOnline
|
||||||
? new Date(stats.updated_at).toLocaleString()
|
? new Date(stats.updated_at).toLocaleString()
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
|
|||||||
title: "内存",
|
title: "内存",
|
||||||
type: "area",
|
type: "area",
|
||||||
value: (
|
value: (
|
||||||
<Flex gap="0" direction="column" align="end" className="text-sm">
|
<Flex gap="0" direction="column" align="end">
|
||||||
<label>
|
<label>
|
||||||
{liveData?.ram?.used
|
{liveData?.ram?.used
|
||||||
? `${formatBytes(liveData.ram.used)} / ${formatBytes(
|
? `${formatBytes(liveData.ram.used)} / ${formatBytes(
|
||||||
@@ -153,7 +153,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
|
|||||||
type: "line",
|
type: "line",
|
||||||
value: (
|
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.up || 0)}/s</span>
|
||||||
<span>↓ {formatBytes(liveData?.network.down || 0)}/s</span>
|
<span>↓ {formatBytes(liveData?.network.down || 0)}/s</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -182,7 +182,7 @@ const LoadCharts = memo(({ node, hours, liveData }: LoadChartsProps) => {
|
|||||||
title: "连接数",
|
title: "连接数",
|
||||||
type: "line",
|
type: "line",
|
||||||
value: (
|
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>TCP: {liveData?.connections.tcp}</span>
|
||||||
<span>UDP: {liveData?.connections.udp}</span>
|
<span>UDP: {liveData?.connections.udp}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
Reference in New Issue
Block a user