mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-18 09:31:55 +08:00
优化 GroupSwitch 组件的标签点击处理逻辑,移除冗余的滚动逻辑,简化代码结构。同时在 Server 页面中引入直接国家选择器,提升用户体验和状态管理的清晰度。
This commit is contained in:
parent
b800ce816a
commit
d5f3548af4
49
src/components/DirectCountrySelect.tsx
Normal file
49
src/components/DirectCountrySelect.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -43,56 +43,28 @@ export default function GroupSwitch({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
// 处理标签点击
|
||||||
const container = scrollRef.current
|
function handleClick(tab: string) {
|
||||||
if (!container) return
|
// 避免重复点击当前选中的标签
|
||||||
|
if (tab === currentTab) return;
|
||||||
|
|
||||||
const isOverflowing = container.scrollWidth > container.clientWidth
|
try {
|
||||||
if (!isOverflowing) return
|
// 直接调用父组件传递的回调
|
||||||
|
setCurrentTab(tab);
|
||||||
|
console.log(`[${isCountrySwitch ? '国家' : '分组'}] 切换到: ${tab}`);
|
||||||
|
|
||||||
const onWheel = (e: WheelEvent) => {
|
// 手动滚动到可见区域
|
||||||
e.preventDefault()
|
const index = tabs.indexOf(tab);
|
||||||
container.scrollLeft += e.deltaY
|
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 (
|
return (
|
||||||
@ -109,7 +81,7 @@ export default function GroupSwitch({
|
|||||||
<div
|
<div
|
||||||
key={isCountrySwitch ? `country-${tab}` : `group-${tab}`}
|
key={isCountrySwitch ? `country-${tab}` : `group-${tab}`}
|
||||||
ref={tagRefs.current[index]}
|
ref={tagRefs.current[index]}
|
||||||
onClick={() => handleTabClick(tab)}
|
onClick={() => handleClick(tab)}
|
||||||
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",
|
||||||
currentTab === tab ? "text-black dark:text-white" : "text-stone-400 dark:text-stone-500",
|
currentTab === tab ? "text-black dark:text-white" : "text-stone-400 dark:text-stone-500",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import GlobalMap from "@/components/GlobalMap"
|
import GlobalMap from "@/components/GlobalMap"
|
||||||
import GroupSwitch from "@/components/GroupSwitch"
|
|
||||||
import ServerCard from "@/components/ServerCard"
|
import ServerCard from "@/components/ServerCard"
|
||||||
import ServerOverview from "@/components/ServerOverview"
|
import ServerOverview from "@/components/ServerOverview"
|
||||||
import { ServiceTracker } from "@/components/ServiceTracker"
|
import { ServiceTracker } from "@/components/ServiceTracker"
|
||||||
@ -17,8 +16,9 @@ import { NezhaWebsocketResponse } from "@/types/nezha-api"
|
|||||||
import { ServerGroup } from "@/types/nezha-api"
|
import { ServerGroup } from "@/types/nezha-api"
|
||||||
import { ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, ChartBarSquareIcon, MapIcon } from "@heroicons/react/20/solid"
|
import { ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, ChartBarSquareIcon, MapIcon } from "@heroicons/react/20/solid"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
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 { useTranslation } from "react-i18next"
|
||||||
|
import DirectCountrySelect from "@/components/DirectCountrySelect"
|
||||||
|
|
||||||
export default function Servers() {
|
export default function Servers() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -52,25 +52,16 @@ export default function Servers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTagChange = (newGroup: string) => {
|
const handleCountryChange = useCallback((newCountry: string) => {
|
||||||
groupRef.current = newGroup
|
countryRef.current = newCountry;
|
||||||
countryRef.current = "All" // 切换组时重置国家筛选
|
|
||||||
|
|
||||||
setCurrentGroup(newGroup)
|
// 强制立即更新状态
|
||||||
setCurrentCountry("All")
|
setCurrentCountry(newCountry);
|
||||||
|
|
||||||
sessionStorage.setItem("selectedGroup", newGroup)
|
// 保存到会话存储
|
||||||
sessionStorage.setItem("selectedCountry", "All")
|
sessionStorage.setItem("selectedCountry", newCountry);
|
||||||
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0))
|
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const showServicesState = localStorage.getItem("showServices")
|
const showServicesState = localStorage.getItem("showServices")
|
||||||
@ -124,6 +115,7 @@ export default function Servers() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lastMessage || !initializedRef.current) return;
|
if (!lastMessage || !initializedRef.current) return;
|
||||||
|
|
||||||
|
// 保持用户选择的筛选状态
|
||||||
setCurrentGroup(groupRef.current)
|
setCurrentGroup(groupRef.current)
|
||||||
setCurrentCountry(countryRef.current)
|
setCurrentCountry(countryRef.current)
|
||||||
}, [lastMessage])
|
}, [lastMessage])
|
||||||
@ -137,15 +129,6 @@ export default function Servers() {
|
|||||||
.sort()
|
.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 = [
|
const countryTabs = [
|
||||||
"All",
|
"All",
|
||||||
...availableCountries.map(code => code.toUpperCase())
|
...availableCountries.map(code => code.toUpperCase())
|
||||||
@ -347,8 +330,6 @@ export default function Servers() {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<GroupSwitch tabs={groupTabs} currentTab={currentGroup} setCurrentTab={handleTagChange} />
|
|
||||||
<GroupSwitch tabs={countryTabs} currentTab={currentCountry} setCurrentTab={handleCountryChange} isCountrySwitch={true} />
|
|
||||||
</section>
|
</section>
|
||||||
<Popover onOpenChange={setSettingsOpen}>
|
<Popover onOpenChange={setSettingsOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@ -411,7 +392,17 @@ export default function Servers() {
|
|||||||
</div>
|
</div>
|
||||||
{showMap === "1" && <GlobalMap now={nezhaWsData.now} serverList={nezhaWsData?.servers || []} />}
|
{showMap === "1" && <GlobalMap now={nezhaWsData.now} serverList={nezhaWsData?.servers || []} />}
|
||||||
{showServices === "1" && <ServiceTracker serverList={filteredServers} />}
|
{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) => {
|
{filteredServers.map((serverInfo) => {
|
||||||
// 查找服务器所属的分组
|
// 查找服务器所属的分组
|
||||||
const serverGroup = groupData?.data?.find(
|
const serverGroup = groupData?.data?.find(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user