mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
feat: optimised scroll position
This commit is contained in:
parent
6e8691f9de
commit
1b868a90b5
@ -10,6 +10,7 @@ import getEnv from "@/lib/env-entry";
|
|||||||
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerDetailClient({
|
export default function ServerDetailClient({
|
||||||
@ -20,6 +21,32 @@ export default function ServerDetailClient({
|
|||||||
const t = useTranslations("ServerDetailClient");
|
const t = useTranslations("ServerDetailClient");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
|
||||||
|
const [hasHistory, setHasHistory] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const previousPath = sessionStorage.getItem("lastPath");
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
if (previousPath && previousPath !== currentPath) {
|
||||||
|
setHasHistory(true);
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem("lastPath", currentPath);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const linkClick = () => {
|
||||||
|
if (hasHistory) {
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
router.push(`/${locale}/`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const { data, error } = useSWR<NezhaAPISafe>(
|
const { data, error } = useSWR<NezhaAPISafe>(
|
||||||
`/api/detail?server_id=${server_id}`,
|
`/api/detail?server_id=${server_id}`,
|
||||||
nezhaFetcher,
|
nezhaFetcher,
|
||||||
@ -46,9 +73,7 @@ export default function ServerDetailClient({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={linkClick}
|
||||||
router.push(`/${locale}/`);
|
|
||||||
}}
|
|
||||||
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
|
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
|
||||||
>
|
>
|
||||||
<BackIcon />
|
<BackIcon />
|
||||||
|
@ -6,17 +6,53 @@ import Switch from "@/components/Switch";
|
|||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { nezhaFetcher } from "@/lib/utils";
|
import { nezhaFetcher } from "@/lib/utils";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerListClient() {
|
export default function ServerListClient() {
|
||||||
const t = useTranslations("ServerListClient");
|
const t = useTranslations("ServerListClient");
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [tag, setTag] = useState<string>(t("defaultTag"));
|
const defaultTag = t("defaultTag");
|
||||||
|
const [tag, setTag] = useState<string>(
|
||||||
|
sessionStorage.getItem("selectedTag") || defaultTag,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTagChange = (newTag: string) => {
|
||||||
|
setTag(newTag);
|
||||||
|
sessionStorage.setItem("selectedTag", newTag);
|
||||||
|
sessionStorage.setItem(
|
||||||
|
"scrollPosition",
|
||||||
|
String(containerRef.current?.scrollTop || 0),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreScrollPosition = () => {
|
||||||
|
const savedPosition = sessionStorage.getItem("scrollPosition");
|
||||||
|
if (savedPosition && containerRef.current) {
|
||||||
|
containerRef.current.scrollTop = Number(savedPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
restoreScrollPosition();
|
||||||
|
}, [tag]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRouteChange = () => {
|
||||||
|
restoreScrollPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("popstate", handleRouteChange);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handleRouteChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
|
const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
|
||||||
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
|
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
@ -24,32 +60,38 @@ export default function ServerListClient() {
|
|||||||
<p className="text-sm font-medium opacity-40">{t("error_message")}</p>
|
<p className="text-sm font-medium opacity-40">{t("error_message")}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!data?.result) return null;
|
if (!data?.result) return null;
|
||||||
|
|
||||||
const { result } = data;
|
const { result } = data;
|
||||||
|
|
||||||
const sortedServers = result.sort((a, b) => {
|
const sortedServers = result.sort((a, b) => {
|
||||||
const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0);
|
const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0);
|
||||||
if (displayIndexDiff !== 0) return displayIndexDiff;
|
if (displayIndexDiff !== 0) return displayIndexDiff;
|
||||||
return a.id - b.id;
|
return a.id - b.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const allTag = sortedServers.map((server) => server.tag).filter((tag) => tag);
|
const allTag = sortedServers.map((server) => server.tag).filter(Boolean);
|
||||||
const uniqueTags = [...new Set(allTag)];
|
const uniqueTags = [...new Set(allTag)];
|
||||||
|
uniqueTags.unshift(defaultTag);
|
||||||
uniqueTags.unshift(t("defaultTag"));
|
|
||||||
|
|
||||||
const filteredServers =
|
const filteredServers =
|
||||||
tag === t("defaultTag")
|
tag === defaultTag
|
||||||
? sortedServers
|
? sortedServers
|
||||||
: sortedServers.filter((server) => server.tag === tag);
|
: sortedServers.filter((server) => server.tag === tag);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
||||||
<Switch allTag={uniqueTags} nowTag={tag} setTag={setTag} />
|
<Switch
|
||||||
|
allTag={uniqueTags}
|
||||||
|
nowTag={tag}
|
||||||
|
onTagChange={handleTagChange}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<section className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
<section
|
||||||
|
ref={containerRef}
|
||||||
|
className="grid grid-cols-1 gap-2 md:grid-cols-2"
|
||||||
|
>
|
||||||
{filteredServers.map((serverInfo) => (
|
{filteredServers.map((serverInfo) => (
|
||||||
<ServerCard key={serverInfo.id} serverInfo={serverInfo} />
|
<ServerCard key={serverInfo.id} serverInfo={serverInfo} />
|
||||||
))}
|
))}
|
||||||
|
@ -2,32 +2,35 @@
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
export default function Switch({
|
export default function Switch({
|
||||||
allTag,
|
allTag,
|
||||||
nowTag,
|
nowTag,
|
||||||
setTag,
|
onTagChange,
|
||||||
}: {
|
}: {
|
||||||
allTag: string[];
|
allTag: string[];
|
||||||
nowTag: string;
|
nowTag: string;
|
||||||
setTag: (tag: string) => void;
|
onTagChange: (tag: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedTag = sessionStorage.getItem("selectedTag");
|
||||||
|
if (savedTag && allTag.includes(savedTag)) {
|
||||||
|
onTagChange(savedTag);
|
||||||
|
}
|
||||||
|
}, [allTag]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = scrollRef.current;
|
const container = scrollRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const isOverflowing = container.scrollWidth > container.clientWidth;
|
const isOverflowing = container.scrollWidth > container.clientWidth;
|
||||||
|
if (!isOverflowing) return;
|
||||||
if (!isOverflowing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onWheel = (e: WheelEvent) => {
|
const onWheel = (e: WheelEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
|
||||||
container.scrollLeft += e.deltaY;
|
container.scrollLeft += e.deltaY;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,7 +50,7 @@ export default function Switch({
|
|||||||
{allTag.map((tag) => (
|
{allTag.map((tag) => (
|
||||||
<div
|
<div
|
||||||
key={tag}
|
key={tag}
|
||||||
onClick={() => setTag(tag)}
|
onClick={() => onTagChange(tag)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500",
|
"relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500",
|
||||||
nowTag === tag
|
nowTag === tag
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3020 --turbo",
|
"dev": "next dev -p 3020",
|
||||||
"start": "node .next/standalone/server.js",
|
"start": "node .next/standalone/server.js",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
||||||
|
Loading…
Reference in New Issue
Block a user