From f9480a13e77868b70ffc9eeec1b4070fa3da1652 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 1 Apr 2026 17:26:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=B9=E5=B9=95=E9=80=89?= =?UTF-8?q?=E9=9B=86=E5=88=86=E7=BB=84=E6=97=A0=E6=B3=95=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E6=BB=91=E8=BD=AE=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++ src/components/DanmakuPanel.tsx | 69 ++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1437f13..f45089d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ public/workbox-*.js.map *.db *.db-shm *.db-wal + +# local scripts +scripts/tvbox/ diff --git a/src/components/DanmakuPanel.tsx b/src/components/DanmakuPanel.tsx index e4eeed4..1984521 100644 --- a/src/components/DanmakuPanel.tsx +++ b/src/components/DanmakuPanel.tsx @@ -35,9 +35,12 @@ export default function DanmakuPanel({ const [searchError, setSearchError] = useState(null); const initializedRef = useRef(false); // 标记是否已初始化过 const fileInputRef = useRef(null); + const episodeGroupContainerRef = useRef(null); + const episodeGroupButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); const [episodeGroupIndex, setEpisodeGroupIndex] = useState(0); const [episodeDescending, setEpisodeDescending] = useState(false); const [episodeViewMode, setEpisodeViewMode] = useState<'list' | 'grid'>('list'); + const [isEpisodeGroupHovered, setIsEpisodeGroupHovered] = useState(false); const episodesPerGroup = 50; // 搜索弹幕 @@ -230,6 +233,62 @@ export default function DanmakuPanel({ return String(episodeNumber); }, []); + const preventPageScroll = useCallback((e: WheelEvent) => { + if (isEpisodeGroupHovered) { + e.preventDefault(); + } + }, [isEpisodeGroupHovered]); + + const handleEpisodeGroupWheel = useCallback((e: WheelEvent) => { + if (!isEpisodeGroupHovered || !episodeGroupContainerRef.current) { + return; + } + + const container = episodeGroupContainerRef.current; + if (container.scrollWidth <= container.clientWidth) { + return; + } + + e.preventDefault(); + container.scrollBy({ + left: e.deltaY * 2, + behavior: 'smooth', + }); + }, [isEpisodeGroupHovered]); + + useEffect(() => { + if (isEpisodeGroupHovered) { + document.addEventListener('wheel', preventPageScroll, { passive: false }); + document.addEventListener('wheel', handleEpisodeGroupWheel, { passive: false }); + } else { + document.removeEventListener('wheel', preventPageScroll); + document.removeEventListener('wheel', handleEpisodeGroupWheel); + } + + return () => { + document.removeEventListener('wheel', preventPageScroll); + document.removeEventListener('wheel', handleEpisodeGroupWheel); + }; + }, [handleEpisodeGroupWheel, isEpisodeGroupHovered, preventPageScroll]); + + useEffect(() => { + const btn = episodeGroupButtonRefs.current[displayEpisodeGroupIndex]; + const container = episodeGroupContainerRef.current; + if (!btn || !container) { + return; + } + + const containerRect = container.getBoundingClientRect(); + const btnRect = btn.getBoundingClientRect(); + const btnLeft = btnRect.left - containerRect.left + container.scrollLeft; + const targetScrollLeft = btnLeft - (containerRect.width - btnRect.width) / 2; + + container.scrollTo({ + left: targetScrollLeft, + behavior: 'smooth', + }); + }, [displayEpisodeGroupIndex]); + return (
{/* 搜索区域 - 固定在顶部 */} @@ -348,12 +407,20 @@ export default function DanmakuPanel({ {!isLoadingEpisodes && episodes.length > 0 && (
-
+
setIsEpisodeGroupHovered(true)} + onMouseLeave={() => setIsEpisodeGroupHovered(false)} + > {episodeGroups.map((label, idx) => { const isActive = idx === displayEpisodeGroupIndex; return (