更新 GroupSwitch 组件,增加暗黑模式支持和国家切换功能,同时优化状态管理逻辑以适应不同的切换需求。在 Server 页面中添加国家筛选功能,提升用户体验。

This commit is contained in:
wood chen 2025-05-07 15:26:40 +08:00
parent 3c6e8c1730
commit 20ca646e9a
2 changed files with 78 additions and 13 deletions

View File

@ -1,21 +1,45 @@
import { cn } from "@/lib/utils"
import { m } from "framer-motion"
import { createRef, useEffect, useRef } from "react"
import { createRef, useEffect, useRef, useState } from "react"
import ServerFlag from "@/components/ServerFlag"
export default function GroupSwitch({
tabs,
currentTab,
setCurrentTab,
isCountrySwitch = false
}: {
tabs: string[]
currentTab: string
setCurrentTab: (tab: string) => void
isCountrySwitch?: boolean
}) {
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
const [isDarkMode, setIsDarkMode] = useState(false)
const scrollRef = useRef<HTMLDivElement>(null)
const tagRefs = useRef(tabs.map(() => createRef<HTMLDivElement>()))
useEffect(() => {
// 检测暗黑模式
setIsDarkMode(document.documentElement.classList.contains('dark'))
// 监听主题变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
setIsDarkMode(document.documentElement.classList.contains('dark'))
}
})
})
observer.observe(document.documentElement, { attributes: true })
return () => {
observer.disconnect()
}
}, [])
useEffect(() => {
const container = scrollRef.current
if (!container) return
@ -36,11 +60,12 @@ export default function GroupSwitch({
}, [])
useEffect(() => {
const savedGroup = sessionStorage.getItem("selectedGroup")
if (savedGroup && tabs.includes(savedGroup)) {
setCurrentTab(savedGroup)
const storageKey = isCountrySwitch ? "selectedCountry" : "selectedGroup"
const savedValue = sessionStorage.getItem(storageKey)
if (savedValue && tabs.includes(savedValue)) {
setCurrentTab(savedValue)
}
}, [tabs, setCurrentTab])
}, [tabs, setCurrentTab, isCountrySwitch])
useEffect(() => {
const currentTagRef = tagRefs.current[tabs.indexOf(currentTab)]
@ -52,7 +77,7 @@ export default function GroupSwitch({
inline: "center",
})
}
}, [currentTab])
}, [currentTab, tabs])
return (
<div ref={scrollRef} className="scrollbar-hidden z-50 flex flex-col items-start overflow-x-scroll rounded-[50px]">
@ -73,15 +98,22 @@ export default function GroupSwitch({
>
{currentTab === tab && (
<m.div
layoutId="tab-switch"
className="absolute inset-0 z-10 h-full w-full content-center bg-white shadow-lg shadow-black/5 dark:bg-stone-700 dark:shadow-white/5"
layoutId={isCountrySwitch ? "country-switch" : "tab-switch"}
style={{
position: "absolute",
inset: 0,
zIndex: 10,
height: "100%",
width: "100%",
backgroundColor: isDarkMode ? "rgb(68 64 60)" : "white", // bg-stone-700 : white
boxShadow: isDarkMode ? "0 1px 3px 0 rgba(255, 255, 255, 0.05)" : "0 1px 3px 0 rgba(0, 0, 0, 0.05)",
originY: "0px",
borderRadius: 46,
}}
/>
)}
<div className="relative z-20 flex items-center gap-1">
{isCountrySwitch && tab !== "All" && <ServerFlag country_code={tab.toLowerCase()} className="text-[10px]" />}
<p className="whitespace-nowrap">{tab}</p>
</div>
</div>

View File

@ -34,6 +34,7 @@ export default function Servers() {
const containerRef = useRef<HTMLDivElement>(null)
const [settingsOpen, setSettingsOpen] = useState<boolean>(false)
const [currentGroup, setCurrentGroup] = useState<string>("All")
const [currentCountry, setCurrentCountry] = useState<string>("All")
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
@ -46,7 +47,15 @@ export default function Servers() {
const handleTagChange = (newGroup: string) => {
setCurrentGroup(newGroup)
setCurrentCountry("All") // 切换组时重置国家筛选
sessionStorage.setItem("selectedGroup", newGroup)
sessionStorage.setItem("selectedCountry", "All")
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0))
}
const handleCountryChange = (newCountry: string) => {
setCurrentCountry(newCountry)
sessionStorage.setItem("selectedCountry", newCountry)
sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0))
}
@ -73,13 +82,22 @@ export default function Servers() {
useEffect(() => {
const savedGroup = sessionStorage.getItem("selectedGroup") || "All"
const savedCountry = sessionStorage.getItem("selectedCountry") || "All"
setCurrentGroup(savedGroup)
setCurrentCountry(savedCountry)
restoreScrollPosition()
}, [])
const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null
// 获取所有可用的国家代码
const availableCountries = nezhaWsData?.servers
? [...new Set(nezhaWsData.servers.map(server => server.country_code))]
.filter(Boolean)
.sort()
: []
const groupTabs = [
"All",
...(groupData?.data
@ -89,6 +107,11 @@ export default function Servers() {
?.map((item: ServerGroup) => item.group.name) || []),
]
const countryTabs = [
"All",
...availableCountries.map(code => code.toUpperCase())
]
// 获取cycle_transfer_stats数据
const { data: serviceData } = useQuery({
queryKey: ["service"],
@ -122,11 +145,20 @@ export default function Servers() {
let filteredServers =
nezhaWsData?.servers?.filter((server) => {
if (currentGroup === "All") return true
const group = groupData?.data?.find(
(g: ServerGroup) => g.group.name === currentGroup && Array.isArray(g.servers) && g.servers.includes(server.id),
)
return !!group
// 组筛选
if (currentGroup !== "All") {
const group = groupData?.data?.find(
(g: ServerGroup) => g.group.name === currentGroup && Array.isArray(g.servers) && g.servers.includes(server.id),
)
if (!group) return false
}
// 国家筛选
if (currentCountry !== "All" && server.country_code?.toUpperCase() !== currentCountry) {
return false
}
return true
}) || []
const totalServers = filteredServers.length || 0
@ -272,6 +304,7 @@ 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>