优化 GroupSwitch 组件的标签点击处理逻辑,移除冗余的滚动逻辑,简化代码结构。同时在 Server 页面中引入直接国家选择器,提升用户体验和状态管理的清晰度。

This commit is contained in:
wood chen 2025-05-07 15:52:25 +08:00
parent b800ce816a
commit d5f3548af4
3 changed files with 93 additions and 81 deletions

View File

@ -0,0 +1,49 @@
import { cn } from "@/lib/utils";
import ServerFlag from "@/components/ServerFlag";
type DirectCountrySelectProps = {
countries: string[];
currentCountry: string;
onChange: (country: string) => void;
};
// 这是一个简单的直接选择组件,避免可能的事件传播问题
export default function DirectCountrySelect({
countries,
currentCountry,
onChange
}: DirectCountrySelectProps) {
return (
<div className="w-full">
<div className="flex flex-wrap gap-1.5 pb-1">
<button
className={cn(
"px-3 py-1.5 text-xs rounded-md transition-all border",
currentCountry === "All"
? "bg-blue-500 text-white border-blue-600 hover:bg-blue-600 shadow-sm"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700"
)}
onClick={() => onChange("All")}
>
ALL
</button>
{countries.map((country) => (
<button
key={country}
className={cn(
"px-3 py-1.5 text-xs rounded-md flex items-center gap-1.5 transition-all border",
currentCountry === country
? "bg-blue-500 text-white border-blue-600 hover:bg-blue-600 shadow-sm"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700"
)}
onClick={() => onChange(country)}
>
<ServerFlag country_code={country.toLowerCase()} className="text-[12px]" />
{country}
</button>
))}
</div>
</div>
);
}

View File

@ -43,56 +43,28 @@ export default function GroupSwitch({
}
}, [])
useEffect(() => {
const container = scrollRef.current
if (!container) return
const isOverflowing = container.scrollWidth > container.clientWidth
if (!isOverflowing) return
const onWheel = (e: WheelEvent) => {
e.preventDefault()
container.scrollLeft += e.deltaY
// 处理标签点击
function handleClick(tab: string) {
// 避免重复点击当前选中的标签
if (tab === currentTab) return;
try {
// 直接调用父组件传递的回调
setCurrentTab(tab);
console.log(`[${isCountrySwitch ? '国家' : '分组'}] 切换到: ${tab}`);
// 手动滚动到可见区域
const index = tabs.indexOf(tab);
if (index !== -1 && tagRefs.current[index]?.current) {
tagRefs.current[index].current?.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center"
});
}
} catch (error) {
console.error('切换标签出错:', error);
}
container.addEventListener("wheel", onWheel, { passive: false })
return () => {
container.removeEventListener("wheel", onWheel)
}
}, [])
useEffect(() => {
const storageKey = isCountrySwitch ? "selectedCountry" : "selectedGroup"
const savedValue = sessionStorage.getItem(storageKey)
if (savedValue && tabs.includes(savedValue)) {
setCurrentTab(savedValue)
}
}, [tabs, setCurrentTab, isCountrySwitch])
// 当tabs变化时更新tagRefs
useEffect(() => {
tagRefs.current = tabs.map(() => createRef<HTMLDivElement>())
}, [tabs])
// 处理选中标签的滚动逻辑
useEffect(() => {
const currentTagIndex = tabs.indexOf(currentTab)
if (currentTagIndex === -1) return // 如果当前选中的标签不在tabs中不执行滚动
const currentTagRef = tagRefs.current[currentTagIndex]
if (currentTagRef && currentTagRef.current) {
currentTagRef.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center",
})
}
}, [currentTab, tabs])
const handleTabClick = (tab: string) => {
if (tab === currentTab) return; // 如果点击的是当前选中的标签,不执行操作
setCurrentTab(tab)
}
return (
@ -109,7 +81,7 @@ export default function GroupSwitch({
<div
key={isCountrySwitch ? `country-${tab}` : `group-${tab}`}
ref={tagRefs.current[index]}
onClick={() => handleTabClick(tab)}
onClick={() => handleClick(tab)}
className={cn(
"relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500",
currentTab === tab ? "text-black dark:text-white" : "text-stone-400 dark:text-stone-500",

View File

@ -1,5 +1,4 @@
import GlobalMap from "@/components/GlobalMap"
import GroupSwitch from "@/components/GroupSwitch"
import ServerCard from "@/components/ServerCard"
import ServerOverview from "@/components/ServerOverview"
import { ServiceTracker } from "@/components/ServiceTracker"
@ -17,8 +16,9 @@ import { NezhaWebsocketResponse } from "@/types/nezha-api"
import { ServerGroup } from "@/types/nezha-api"
import { ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, ChartBarSquareIcon, MapIcon } from "@heroicons/react/20/solid"
import { useQuery } from "@tanstack/react-query"
import { useEffect, useRef, useState } from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import { useTranslation } from "react-i18next"
import DirectCountrySelect from "@/components/DirectCountrySelect"
export default function Servers() {
const { t } = useTranslation()
@ -52,25 +52,16 @@ export default function Servers() {
}
}
const handleTagChange = (newGroup: string) => {
groupRef.current = newGroup
countryRef.current = "All" // 切换组时重置国家筛选
const handleCountryChange = useCallback((newCountry: string) => {
countryRef.current = newCountry;
setCurrentGroup(newGroup)
setCurrentCountry("All")
// 强制立即更新状态
setCurrentCountry(newCountry);
sessionStorage.setItem("selectedGroup", newGroup)
sessionStorage.setItem("selectedCountry", "All")
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0))
}
const handleCountryChange = (newCountry: string) => {
countryRef.current = newCountry
setCurrentCountry(newCountry)
sessionStorage.setItem("selectedCountry", newCountry)
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0))
}
// 保存到会话存储
sessionStorage.setItem("selectedCountry", newCountry);
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0));
}, []);
useEffect(() => {
const showServicesState = localStorage.getItem("showServices")
@ -124,6 +115,7 @@ export default function Servers() {
useEffect(() => {
if (!lastMessage || !initializedRef.current) return;
// 保持用户选择的筛选状态
setCurrentGroup(groupRef.current)
setCurrentCountry(countryRef.current)
}, [lastMessage])
@ -137,15 +129,6 @@ export default function Servers() {
.sort()
: []
const groupTabs = [
"All",
...(groupData?.data
?.filter((item: ServerGroup) => {
return Array.isArray(item.servers) && item.servers.some((serverId) => nezhaWsData?.servers?.some((server) => server.id === serverId))
})
?.map((item: ServerGroup) => item.group.name) || []),
]
const countryTabs = [
"All",
...availableCountries.map(code => code.toUpperCase())
@ -347,8 +330,6 @@ export default function Servers() {
})}
/>
</button>
<GroupSwitch tabs={groupTabs} currentTab={currentGroup} setCurrentTab={handleTagChange} />
<GroupSwitch tabs={countryTabs} currentTab={currentCountry} setCurrentTab={handleCountryChange} isCountrySwitch={true} />
</section>
<Popover onOpenChange={setSettingsOpen}>
<PopoverTrigger asChild>
@ -411,7 +392,17 @@ export default function Servers() {
</div>
{showMap === "1" && <GlobalMap now={nezhaWsData.now} serverList={nezhaWsData?.servers || []} />}
{showServices === "1" && <ServiceTracker serverList={filteredServers} />}
<section ref={containerRef} className="grid grid-cols-1 gap-4 md:grid-cols-3 mt-6 server-card-list">
{/* 优化直接国家选择器 */}
<div className="mt-4">
<DirectCountrySelect
countries={countryTabs.filter(tab => tab !== "All")}
currentCountry={currentCountry}
onChange={handleCountryChange}
/>
</div>
<section ref={containerRef} className="grid grid-cols-1 gap-4 md:grid-cols-3 mt-4 server-card-list">
{filteredServers.map((serverInfo) => {
// 查找服务器所属的分组
const serverGroup = groupData?.data?.find(