claude-code-cchui/src/components/markdown/MarkdownRenderer.tsx
gaoziman 4eab17155e style(字体): 优化字体系统使用相对单位
- 移除 ChatInput 和 MessageBubble 中的固定字体大小类
- MarkdownRenderer 标题和内容使用 em 相对单位
- 表格字体大小改为相对单位保持比例
- 聊天输入框添加 z-20 层级避免被代码块遮挡
2025-12-21 02:47:13 +08:00

223 lines
5.3 KiB
TypeScript

'use client';
import { memo } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { CodeBlock } from './CodeBlock';
import { cn } from '@/lib/utils';
interface MarkdownRendererProps {
content: string;
className?: string;
}
// 将 components 配置提取到组件外部,避免每次渲染时创建新对象
const markdownComponents = {
// 代码块
code({ className, children, ...props }: { className?: string; children?: React.ReactNode }) {
const match = /language-(\w+)/.exec(className || '');
const isInline = !match && !className;
if (isInline) {
// 行内代码 - 无特殊样式,仅等宽字体
return (
<code
className="font-mono"
{...props}
>
{children}
</code>
);
}
// 代码块
return (
<CodeBlock
code={String(children).replace(/\n$/, '')}
language={match ? match[1] : 'text'}
/>
);
},
// 段落
p({ children }: { children?: React.ReactNode }) {
return (
<p className="mb-3 last:mb-0 leading-[1.7]">
{children}
</p>
);
},
// 标题 - 使用相对单位保持与全局字体的比例
h1({ children }: { children?: React.ReactNode }) {
return (
<h1 className="text-[1.25em] font-bold mt-5 mb-3 text-[var(--color-text-primary)]">
{children}
</h1>
);
},
h2({ children }: { children?: React.ReactNode }) {
return (
<h2 className="text-[1.125em] font-bold mt-4 mb-2 text-[var(--color-text-primary)]">
{children}
</h2>
);
},
h3({ children }: { children?: React.ReactNode }) {
return (
<h3 className="text-[1em] font-semibold mt-3 mb-2 text-[var(--color-text-primary)]">
{children}
</h3>
);
},
h4({ children }: { children?: React.ReactNode }) {
return (
<h4 className="text-[1em] font-semibold mt-2 mb-1 text-[var(--color-text-primary)]">
{children}
</h4>
);
},
// 列表
ul({ children }: { children?: React.ReactNode }) {
return (
<ul className="list-disc pl-5 my-2 space-y-1 marker:text-[#E06B3E]">
{children}
</ul>
);
},
ol({ children }: { children?: React.ReactNode }) {
return (
<ol className="list-decimal pl-5 my-2 space-y-1 marker:text-[#E06B3E]">
{children}
</ol>
);
},
li({ children }: { children?: React.ReactNode }) {
return (
<li className="leading-relaxed">
{children}
</li>
);
},
// 链接
a({ href, children }: { href?: string; children?: React.ReactNode }) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-[var(--color-primary)] hover:underline"
>
{children}
</a>
);
},
// 粗体
strong({ children }: { children?: React.ReactNode }) {
return (
<strong className="font-semibold">
{children}
</strong>
);
},
// 斜体
em({ children }: { children?: React.ReactNode }) {
return (
<em className="italic">
{children}
</em>
);
},
// 引用
blockquote({ children }: { children?: React.ReactNode }) {
return (
<blockquote className="border-l-3 border-[var(--color-primary)] pl-3 my-3 italic text-[var(--color-text-secondary)]">
{children}
</blockquote>
);
},
// 分割线
hr() {
return (
<hr className="my-4 border-[var(--color-border)]" />
);
},
// 表格
table({ children }: { children?: React.ReactNode }) {
return (
<div className="overflow-x-auto my-3">
<table className="min-w-full border border-[var(--color-border)] rounded-lg overflow-hidden text-[0.9em]">
{children}
</table>
</div>
);
},
thead({ children }: { children?: React.ReactNode }) {
return (
<thead className="bg-[var(--color-bg-tertiary)]">
{children}
</thead>
);
},
tbody({ children }: { children?: React.ReactNode }) {
return (
<tbody className="divide-y divide-[var(--color-border)]">
{children}
</tbody>
);
},
tr({ children }: { children?: React.ReactNode }) {
return (
<tr className="hover:bg-[var(--color-bg-hover)] transition-colors">
{children}
</tr>
);
},
th({ children }: { children?: React.ReactNode }) {
return (
<th className="px-3 py-1.5 text-left text-[0.85em] font-semibold text-[var(--color-text-primary)]">
{children}
</th>
);
},
td({ children }: { children?: React.ReactNode }) {
return (
<td className="px-3 py-1.5 text-[0.85em] text-[var(--color-text-secondary)]">
{children}
</td>
);
},
// 图片
img(props: React.ImgHTMLAttributes<HTMLImageElement>) {
return (
<img
{...props}
alt={props.alt || ''}
className="max-w-full h-auto rounded-lg my-3"
/>
);
},
};
// 使用 memo 包裹组件,避免不必要的重渲染
export const MarkdownRenderer = memo(function MarkdownRenderer({ content, className }: MarkdownRendererProps) {
return (
<div className={cn('markdown-content', className)}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={markdownComponents}
>
{content}
</ReactMarkdown>
</div>
);
});