From 6e3bb9e361be75ba0ca164c1fea1208e3829a8ef Mon Sep 17 00:00:00 2001 From: xuncha <1658671838@qq.com> Date: Sat, 31 Jan 2026 15:24:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E7=89=87=E8=A7=A3=E5=AF=86=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=9B=B4=E5=8A=A0=E6=BF=80=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ChatPage.tsx | 93 ++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 8709aca..257e7cd 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -1501,6 +1501,10 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o const imageClickTimerRef = useRef(null) const imageContainerRef = useRef(null) const imageAutoDecryptTriggered = useRef(false) + const imageAutoHdTriggered = useRef(null) + const [imageInView, setImageInView] = useState(false) + const imageForceHdAttempted = useRef(null) + const imageForceHdPending = useRef(false) const [voiceError, setVoiceError] = useState(false) const [voiceLoading, setVoiceLoading] = useState(false) const [isVoicePlaying, setIsVoicePlaying] = useState(false) @@ -1697,10 +1701,13 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o } }, [isEmoji, message.emojiCdnUrl, emojiLocalPath, emojiLoading, emojiError]) - const requestImageDecrypt = useCallback(async (forceUpdate = false) => { - if (!isImage || imageLoading) return - setImageLoading(true) - setImageError(false) + const requestImageDecrypt = useCallback(async (forceUpdate = false, silent = false) => { + if (!isImage) return + if (imageLoading) return + if (!silent) { + setImageLoading(true) + setImageError(false) + } try { if (message.imageMd5 || message.imageDatName) { const result = await window.electronAPI.image.decrypt({ @@ -1726,14 +1733,25 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o setImageHasUpdate(false) return } - setImageError(true) + if (!silent) setImageError(true) } catch { - setImageError(true) + if (!silent) setImageError(true) } finally { - setImageLoading(false) + if (!silent) setImageLoading(false) } }, [isImage, imageLoading, message.imageMd5, message.imageDatName, message.localId, session.username, imageCacheKey, detectImageMimeFromBase64]) + const triggerForceHd = useCallback(() => { + if (!message.imageMd5 && !message.imageDatName) return + if (imageForceHdAttempted.current === imageCacheKey) return + if (imageForceHdPending.current) return + imageForceHdAttempted.current = imageCacheKey + imageForceHdPending.current = true + requestImageDecrypt(true, true).finally(() => { + imageForceHdPending.current = false + }) + }, [imageCacheKey, message.imageDatName, message.imageMd5, requestImageDecrypt]) + const handleImageClick = useCallback(() => { if (imageClickTimerRef.current) { window.clearTimeout(imageClickTimerRef.current) @@ -1846,6 +1864,47 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o return () => observer.disconnect() }, [isImage, imageLocalPath, message.imageMd5, message.imageDatName, requestImageDecrypt]) + // 进入视野时自动尝试切换高清图 + useEffect(() => { + if (!isImage) return + const container = imageContainerRef.current + if (!container) return + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0] + setImageInView(entry.isIntersecting) + }, + { rootMargin: '120px', threshold: 0 } + ) + observer.observe(container) + return () => observer.disconnect() + }, [isImage]) + + useEffect(() => { + if (!isImage || !imageHasUpdate || !imageInView) return + if (imageAutoHdTriggered.current === imageCacheKey) return + imageAutoHdTriggered.current = imageCacheKey + triggerForceHd() + }, [isImage, imageHasUpdate, imageInView, imageCacheKey, triggerForceHd]) + + useEffect(() => { + if (!isImage || !showImagePreview || !imageHasUpdate) return + if (imageAutoHdTriggered.current === imageCacheKey) return + imageAutoHdTriggered.current = imageCacheKey + triggerForceHd() + }, [isImage, showImagePreview, imageHasUpdate, imageCacheKey, triggerForceHd]) + + // 更激进:进入视野/打开预览时,无论 hasUpdate 与否都尝试强制高清 + useEffect(() => { + if (!isImage || !imageInView) return + triggerForceHd() + }, [isImage, imageInView, triggerForceHd]) + + useEffect(() => { + if (!isImage || !showImagePreview) return + triggerForceHd() + }, [isImage, showImagePreview, triggerForceHd]) + useEffect(() => { if (!isVoice) return @@ -2196,23 +2255,15 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o src={imageLocalPath} alt="图片" className="image-message" - onClick={() => setShowImagePreview(true)} + onClick={() => { + if (imageHasUpdate) { + void requestImageDecrypt(true, true) + } + setShowImagePreview(true) + }} onLoad={() => setImageError(false)} onError={() => setImageError(true)} /> - {imageHasUpdate && ( - - )} {showImagePreview && ( setShowImagePreview(false)} />