diff --git a/web/src/components/Chat/MessageBubble.tsx b/web/src/components/Chat/MessageBubble.tsx index 54214a3..5f7bc7d 100644 --- a/web/src/components/Chat/MessageBubble.tsx +++ b/web/src/components/Chat/MessageBubble.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, memo } from 'react'; +import { createPortal } from 'react-dom'; import { CheckCircle, ChevronRight, @@ -319,9 +320,14 @@ function ImageLightbox({ URL.revokeObjectURL(url); }; - return ( + // 必须 portal 到 body:本组件渲染在虚拟化消息行内,行元素带 + // transform(translateY),transformed 祖先会成为 fixed 后代的包含块, + // 导致遮罩只覆盖该行区域、工具栏定位到行盒而非视口、其他行内容叠在 + // 遮罩之上。portal 后 fixed/z-index 相对视口全局生效。 + // z-[10000]:高于下拉选择器(z-[9999])等全局浮层。 + return createPortal(
{/* 顶部工具栏 */} @@ -349,7 +355,8 @@ function ImageLightbox({ onClick={(e) => e.stopPropagation()} alt="图片预览" /> -
+ , + document.body, ); } diff --git a/web/src/components/Chat/ToolDetailModal.tsx b/web/src/components/Chat/ToolDetailModal.tsx index 01b8048..fbb089e 100644 --- a/web/src/components/Chat/ToolDetailModal.tsx +++ b/web/src/components/Chat/ToolDetailModal.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; import { X, Terminal, Clock, Maximize2 } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -65,9 +66,12 @@ export function ToolDetailModal({ ? 'var(--accent-amber)' : 'var(--text-muted)'; - return ( + // portal 到 body:在虚拟化消息行内打开时,行元素的 transform 会成为 + // fixed 后代的包含块,遮罩/定位全部失效(同 ImageLightbox)。 + // z-[10000]:高于下拉选择器(z-[9999])等全局浮层。 + return createPortal(
{/* Modal container */} @@ -145,6 +149,7 @@ export function ToolDetailModal({
- + , + document.body, ); }