From b3d5a47072679ba29249f70c05c33a1ba2eb01e5 Mon Sep 17 00:00:00 2001 From: gaoziman <2942894660@qq.com> Date: Sun, 21 Dec 2025 02:47:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BB=A3=E7=A0=81=E5=9D=97):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=95=BF=E4=BB=A3=E7=A0=81=E6=8A=98=E5=8F=A0/?= =?UTF-8?q?=E5=B1=95=E5=BC=80=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 超过60行代码自动折叠,预览前30行 - 添加渐变遮罩和展开按钮交互 - 显示总行数和剩余行数信息 - 展开状态下显示收起按钮 - 复制功能始终复制完整代码 - 优化代码区域布局使用 flex 结构 --- src/components/markdown/CodeBlock.tsx | 144 ++++++++++++++++++++------ 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/src/components/markdown/CodeBlock.tsx b/src/components/markdown/CodeBlock.tsx index 6d548b0..975bf1e 100644 --- a/src/components/markdown/CodeBlock.tsx +++ b/src/components/markdown/CodeBlock.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef, useState, useMemo } from 'react'; -import { Copy, Check, Eye } from 'lucide-react'; +import { Copy, Check, Eye, ChevronDown, ChevronUp } from 'lucide-react'; import { cn } from '@/lib/utils'; import Prism from 'prismjs'; import { HtmlPreviewModal } from '@/components/ui/HtmlPreviewModal'; @@ -30,6 +30,10 @@ interface CodeBlockProps { language?: string; showLineNumbers?: boolean; className?: string; + /** 折叠阈值,超过此行数自动折叠,默认 60 */ + maxCollapsedLines?: number; + /** 折叠时预览的行数,默认 30 */ + previewLines?: number; } // 语言别名映射 @@ -51,9 +55,12 @@ export function CodeBlock({ language = 'text', showLineNumbers = true, className, + maxCollapsedLines = 60, + previewLines = 30, }: CodeBlockProps) { const [copied, setCopied] = useState(false); const [previewOpen, setPreviewOpen] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); // 规范化语言名称 const normalizedLanguage = languageAliases[language.toLowerCase()] || language.toLowerCase(); @@ -62,17 +69,33 @@ export function CodeBlock({ const isHtmlPreviewable = ['html', 'htm', 'markup'].includes(normalizedLanguage) || ['html', 'htm'].includes(language.toLowerCase()); + const lines = code.split('\n'); + const totalLines = lines.length; + const shouldCollapse = totalLines > maxCollapsedLines; + const remainingLines = totalLines - previewLines; + + // 根据折叠状态获取显示的代码 + const displayCode = useMemo(() => { + if (shouldCollapse && !isExpanded) { + return lines.slice(0, previewLines).join('\n'); + } + return code; + }, [code, lines, shouldCollapse, isExpanded, previewLines]); + + const displayLines = displayCode.split('\n'); + // 使用 useMemo 缓存高亮后的 HTML,避免频繁重新高亮 const highlightedCode = useMemo(() => { const grammar = Prism.languages[normalizedLanguage]; if (grammar) { - return Prism.highlight(code, grammar, normalizedLanguage); + return Prism.highlight(displayCode, grammar, normalizedLanguage); } - return code; - }, [code, normalizedLanguage]); + return displayCode; + }, [displayCode, normalizedLanguage]); const handleCopy = async () => { try { + // 始终复制完整代码 await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); @@ -81,13 +104,15 @@ export function CodeBlock({ } }; - const lines = code.split('\n'); + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; return (
{/* 顶部工具栏 */} -
{language || 'code'} + {/* 显示总行数 */} + {shouldCollapse && ( + + ({totalLines} 行) + + )}
{/* 右侧:操作按钮 */} @@ -145,40 +176,93 @@ export function CodeBlock({ {/* 代码区域 */} -
- {showLineNumbers && ( -
-
- {lines.map((_, index) => ( +
+ {/* 代码内容 - 使用 flex 布局 */} +
+ {/* 行号列 */} + {showLineNumbers && ( +
+ {displayLines.map((_, index) => (
{index + 1}
))}
+ )} + {/* 代码列 */} +
+            
+          
+
+ + {/* 渐变遮罩和展开按钮(仅在折叠状态下显示) */} + {shouldCollapse && !isExpanded && ( + <> + {/* 渐变遮罩 */} +
+ {/* 展开按钮 */} +
+ +
+ + )} + + {/* 收起按钮(仅在展开状态下显示) */} + {shouldCollapse && isExpanded && ( +
+
)} -
-          
-        
{/* HTML 预览模态框 */}