From 0f8fd2ce1fbd4f2f7635ab2be058f1f5d0f0e18a Mon Sep 17 00:00:00 2001 From: gaoziman <2942894660@qq.com> Date: Wed, 24 Dec 2025 22:51:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=81=8A=E5=A4=A9=E9=A1=B5=E9=9D=A2):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 聊天页面支持通过 URL 参数定位消息 - MessageBubble 组件添加高亮状态支持 - 新增消息高亮动画样式,支持亮色/暗色主题 - 跳转后自动滚动到目标消息并高亮闪烁 - 3秒后自动清除高亮效果 --- src/app/chat/[id]/page.tsx | 36 +++++++++++++++++++++++ src/app/globals.css | 33 +++++++++++++++++++++ src/components/features/MessageBubble.tsx | 20 +++++++++++-- 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index 7536214..1c36388 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -30,14 +30,19 @@ export default function ChatPage({ params }: PageProps) { const router = useRouter(); const searchParams = useSearchParams(); const initialMessage = searchParams.get('message'); + const highlightParam = searchParams.get('highlight'); // 搜索高亮参数 const { user } = useAuth(); const { setOptimizedPrompt } = usePromptOptimizer(); const [sidebarOpen, setSidebarOpen] = useState(true); const messagesEndRef = useRef(null); + const messagesContainerRef = useRef(null); const [isNewChat, setIsNewChat] = useState(false); const [initialMessageSent, setInitialMessageSent] = useState(false); + // 高亮消息状态 + const [highlightedMessageId, setHighlightedMessageId] = useState(null); + // 标题下拉菜单状态 const [titleMenuOpen, setTitleMenuOpen] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false); @@ -128,6 +133,36 @@ export default function ChatPage({ params }: PageProps) { scrollToBottom(); }, [messages]); + // 处理搜索高亮:滚动到目标消息并高亮 + useEffect(() => { + if (highlightParam && messages.length > 0) { + // 查找目标消息 + const targetMessage = messages.find(m => m.id === highlightParam); + if (targetMessage) { + // 设置高亮状态 + setHighlightedMessageId(highlightParam); + + // 延迟滚动,确保 DOM 已渲染 + setTimeout(() => { + const messageElement = document.querySelector(`[data-message-id="${highlightParam}"]`); + if (messageElement) { + messageElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, 100); + + // 清除 URL 中的 highlight 参数 + router.replace(`/chat/${chatId}`, { scroll: false }); + + // 3秒后清除高亮状态 + const timer = setTimeout(() => { + setHighlightedMessageId(null); + }, 3000); + + return () => clearTimeout(timer); + } + } + }, [highlightParam, messages, chatId, router]); + // 聚焦标题输入框 useEffect(() => { if (isEditingTitle && titleInputRef.current) { @@ -595,6 +630,7 @@ export default function ChatPage({ params }: PageProps) { onSaveToNote={message.role === 'assistant' && !isStreaming ? handleSaveToNote : undefined} onLinkClick={handleLinkClick} conversationId={chatId} + isHighlighted={highlightedMessageId === message.id} /> )) )} diff --git a/src/app/globals.css b/src/app/globals.css index 5c0484c..2f80556 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -616,3 +616,36 @@ pre[class*="language-"] { [data-theme="dark"] .token.italic { font-style: italic; } + +/* ======================================== + 消息搜索高亮动画 + 用于搜索跳转后高亮显示目标消息 + 风格:轻量闪烁,类似搜索引擎高亮 + ======================================== */ +@keyframes messageHighlight { + 0% { + background-color: rgba(255, 235, 120, 0.6); + } + 100% { + background-color: transparent; + } +} + +.message-highlight { + animation: messageHighlight 2.5s ease-out forwards; + border-radius: var(--radius-md); +} + +/* 暗色模式下使用更暗的黄色 */ +[data-theme="dark"] .message-highlight { + animation-name: messageHighlightDark; +} + +@keyframes messageHighlightDark { + 0% { + background-color: rgba(255, 200, 50, 0.25); + } + 100% { + background-color: transparent; + } +} diff --git a/src/components/features/MessageBubble.tsx b/src/components/features/MessageBubble.tsx index d7a0349..67851c5 100644 --- a/src/components/features/MessageBubble.tsx +++ b/src/components/features/MessageBubble.tsx @@ -56,6 +56,8 @@ interface MessageBubbleProps { onLinkClick?: (url: string) => void; /** 对话 ID(用于关联笔记来源) */ conversationId?: string; + /** 是否高亮显示(搜索跳转时使用) */ + isHighlighted?: boolean; } // 格式化文件大小 @@ -75,7 +77,7 @@ function getDocumentIcon(type: string) { return FileText; } -export function MessageBubble({ message, user, thinkingContent, isStreaming, error, images, searchImages, searchVideos, uploadedImages, uploadedDocuments, usedTools, pyodideStatus, onRegenerate, onSaveToNote, onLinkClick, conversationId }: MessageBubbleProps) { +export function MessageBubble({ message, user, thinkingContent, isStreaming, error, images, searchImages, searchVideos, uploadedImages, uploadedDocuments, usedTools, pyodideStatus, onRegenerate, onSaveToNote, onLinkClick, conversationId, isHighlighted }: MessageBubbleProps) { const isUser = message.role === 'user'; const [thinkingExpanded, setThinkingExpanded] = useState(false); const [copied, setCopied] = useState(false); @@ -138,7 +140,13 @@ export function MessageBubble({ message, user, thinkingContent, isStreaming, err if (isUser) { return ( -
+
{/* 用户上传的图片 */} {uploadedImages && uploadedImages.length > 0 && ( @@ -227,7 +235,13 @@ export function MessageBubble({ message, user, thinkingContent, isStreaming, err } return ( -
+
{/* AI 图标 */}