From dd7fb92fa9e9123769ba74e17f3dae4711215438 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sun, 14 Dec 2025 19:48:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8D=A2=E6=BA=90=E5=A2=9E=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E6=B5=8B=E8=AF=95=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=BD=91?= =?UTF-8?q?=E9=80=9F=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EpisodeSelector.tsx | 147 +++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 20 deletions(-) diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx index 784afcf..88a6a12 100644 --- a/src/components/EpisodeSelector.tsx +++ b/src/components/EpisodeSelector.tsx @@ -82,6 +82,12 @@ const EpisodeSelector: React.FC = ({ const [attemptedSources, setAttemptedSources] = useState>( new Set() ); + // 存储正在重新测试的源 + const [retestingSources, setRetestingSources] = useState>( + new Set() + ); + // 标记初始测速是否已完成 + const [initialTestingCompleted, setInitialTestingCompleted] = useState(false); // 使用 ref 来避免闭包问题 const attemptedSourcesRef = useRef>(new Set()); @@ -229,11 +235,16 @@ const EpisodeSelector: React.FC = ({ const batch = pendingSources.slice(start, start + batchSize); await Promise.all(batch.map(getVideoInfo)); } + + // 初始测速完成后,标记为已完成 + if (!initialTestingCompleted) { + setInitialTestingCompleted(true); + } }; fetchVideoInfosInBatches(); // 依赖项保持与之前一致 - }, [activeTab, availableSources, getVideoInfo, optimizationEnabled]); + }, [activeTab, availableSources, getVideoInfo, optimizationEnabled, initialTestingCompleted]); // 升序分页标签 const categoriesAsc = useMemo(() => { @@ -359,6 +370,58 @@ const EpisodeSelector: React.FC = ({ [onSourceChange] ); + // 解析网速字符串,转换为 KB/s 数值用于排序 + const parseSpeedToKBps = useCallback((speedStr: string): number => { + if (!speedStr || speedStr === '未知' || speedStr === '测量中...') { + return -1; // 无效速度返回 -1,排在最后 + } + + const match = speedStr.match(/^([\d.]+)\s*(KB\/s|MB\/s)$/); + if (!match) { + return -1; + } + + const value = parseFloat(match[1]); + const unit = match[2]; + + // 统一转换为 KB/s + return unit === 'MB/s' ? value * 1024 : value; + }, []); + + // 重新测试单个源 + const handleRetestSource = useCallback( + async (source: SearchResult, e: React.MouseEvent) => { + e.stopPropagation(); // 阻止事件冒泡,避免触发换源 + const sourceKey = `${source.source}-${source.id}`; + + // 标记为正在测试 + setRetestingSources((prev) => new Set(prev).add(sourceKey)); + + // 从已尝试列表中移除,允许重新测试 + setAttemptedSources((prev) => { + const newSet = new Set(prev); + newSet.delete(sourceKey); + return newSet; + }); + + // 同步更新 ref + attemptedSourcesRef.current.delete(sourceKey); + + // 执行测试 + try { + await getVideoInfo(source); + } finally { + // 无论成功或失败,都移除测试标记 + setRetestingSources((prev) => { + const newSet = new Set(prev); + newSet.delete(sourceKey); + return newSet; + }); + } + }, + [getVideoInfo] + ); + const currentStart = currentPage * episodesPerPage + 1; const currentEnd = Math.min( currentStart + episodesPerPage - 1, @@ -575,8 +638,25 @@ const EpisodeSelector: React.FC = ({ const bIsCurrent = b.source?.toString() === currentSource?.toString() && b.id?.toString() === currentId?.toString(); + + // 当前源始终置顶 if (aIsCurrent && !bIsCurrent) return -1; if (!aIsCurrent && bIsCurrent) return 1; + + // 如果初始测速已完成,按网速排序(快的在前) + if (initialTestingCompleted) { + const aKey = `${a.source}-${a.id}`; + const bKey = `${b.source}-${b.id}`; + const aInfo = videoInfoMap.get(aKey); + const bInfo = videoInfoMap.get(bKey); + + const aSpeed = aInfo ? parseSpeedToKBps(aInfo.loadSpeed) : -1; + const bSpeed = bInfo ? parseSpeedToKBps(bInfo.loadSpeed) : -1; + + // 速度快的排在前面(降序) + return bSpeed - aSpeed; + } + return 0; }) .map((source, index) => { @@ -678,30 +758,57 @@ const EpisodeSelector: React.FC = ({ {/* 网络信息 - 底部 */} -
+
+
+ {(() => { + const sourceKey = `${source.source}-${source.id}`; + const videoInfo = videoInfoMap.get(sourceKey); + if (videoInfo) { + if (!videoInfo.hasError) { + return ( +
+
+ {videoInfo.loadSpeed} +
+
+ {videoInfo.pingTime}ms +
+
+ ); + } else { + return ( +
+ 无测速数据 +
+ ); + } + } + return null; + })()} +
+ {/* 重新测试按钮 */} {(() => { const sourceKey = `${source.source}-${source.id}`; + const isTesting = retestingSources.has(sourceKey); const videoInfo = videoInfoMap.get(sourceKey); + + // 只有第一次测试完成后(有测速数据)才显示重新测试按钮 if (videoInfo) { - if (!videoInfo.hasError) { - return ( -
-
- {videoInfo.loadSpeed} -
-
- {videoInfo.pingTime}ms -
-
- ); - } else { - return ( -
- 无测速数据 -
- ); // 占位div - } + return ( + + ); } + return null; })()}