ui(chat): MarkdownContent 支持表格渲染
AI 聊天回复中常出现 Markdown 表格语法 (| h1 | h2 | + |---|---| ), 但 MarkdownContent 仅支持 headers/lists/code,未渲染表格, 显示为原始 | 分隔文本。 修复: 1. ChatPage.tsx MarkdownContent 新增表格解析步骤(步骤 2.7) - 匹配 | header | |---|---| 的多行表格块 - 解析对齐方式 (:---, :---:, ---:) - 生成 <table><thead><tbody> HTML 2. 步骤 10 恢复占位符,步骤 11 解包 <table> 不被 <p> 包裹 3. ChatPage.css 新增 .md-table 样式 - 蓝色渐变表头 + 斑马纹 + hover 高亮 - 用户消息气泡内深色适配
This commit is contained in:
1 parent
845355e4e7
commit
8486a2a0e5
2 files changed
+99
No files matched your search
@@ -446,6 +446,67 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.md-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
font-size: var(--text-sm);
|
||||
background: #fff;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.md-table thead {
|
||||
background: linear-gradient(135deg, #5B5FED 0%, #7B7FEE 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.md-table th {
|
||||
padding: 10px 14px;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.md-table td {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #EEF0F4;
|
||||
color: var(--color-text-primary);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.md-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.md-table tbody tr:hover {
|
||||
background: #F8F9FC;
|
||||
}
|
||||
|
||||
.md-table tbody tr:nth-child(even) {
|
||||
background: #FAFBFD;
|
||||
}
|
||||
|
||||
.md-table tbody tr:nth-child(even):hover {
|
||||
background: #F0F2F7;
|
||||
}
|
||||
|
||||
.message-bubble.user .md-table {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: inherit;
|
||||
}
|
||||
.message-bubble.user .md-table th,
|
||||
.message-bubble.user .md-table td {
|
||||
color: inherit;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.message-bubble.user .md-table tbody tr:hover,
|
||||
.message-bubble.user .md-table tbody tr:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.md-hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
@@ -173,6 +173,42 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => {
|
||||
return '\x00MEDIA' + (mediaBlocks.length - 1) + '\x00';
|
||||
});
|
||||
|
||||
// 2.7 Extract Markdown tables: | h1 | h2 |\n|---|---| ... |
|
||||
// 把整段表格替换为占位符,表格内容(已转义)放入 tableBlocks 数组
|
||||
const tableBlocks: string[] = [];
|
||||
text = text.replace(
|
||||
/((?:^\|.+\|\r?\n)+^\|[\s\-:|]+\|\r?\n(?:^\|.+\|\r?\n?)+)/gm,
|
||||
(_m: string, block: string) => {
|
||||
const lines = block.trim().split(/\r?\n/).filter((l: string) => l.trim());
|
||||
if (lines.length < 2) return block;
|
||||
const headerCells = lines[0].split('|').slice(1, -1).map((c: string) => c.trim());
|
||||
const alignCells = lines[1].split('|').slice(1, -1).map((c: string) => {
|
||||
const t = c.trim();
|
||||
const left = t.startsWith(':');
|
||||
const right = t.endsWith(':');
|
||||
if (left && right) return 'center';
|
||||
if (right) return 'right';
|
||||
if (left) return 'left';
|
||||
return 'left';
|
||||
});
|
||||
const bodyRows = lines.slice(2).map((row: string) =>
|
||||
row.split('|').slice(1, -1).map((c: string) => c.trim())
|
||||
);
|
||||
const thead = headerCells.map((c: string, i: number) =>
|
||||
`<th style="text-align:${alignCells[i] || 'left'}">${c}</th>`
|
||||
).join('');
|
||||
const tbody = bodyRows.map((r: string[]) =>
|
||||
'<tr>' + r.map((c: string, i: number) =>
|
||||
`<td style="text-align:${alignCells[i] || 'left'}">${c}</td>`
|
||||
).join('') + '</tr>'
|
||||
).join('');
|
||||
const html = `<table class="md-table"><thead><tr>${thead}</tr></thead><tbody>${tbody}</tbody></table>`;
|
||||
const idx = tableBlocks.length;
|
||||
tableBlocks.push(html);
|
||||
return '\x00TABLE' + idx + '\x00';
|
||||
}
|
||||
);
|
||||
|
||||
// 3. Headers
|
||||
text = text.replace(/^#### (.+)$/gm, '<h4 class="md-h4">$1</h4>');
|
||||
text = text.replace(/^### (.+)$/gm, '<h4 class="md-h4">$1</h4>');
|
||||
@@ -206,6 +242,7 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => {
|
||||
// 10. Restore code blocks and inline codes
|
||||
text = text.replace(/\x00CODEBLOCK(\d+)\x00/g, (_m: string, idx: string) => codeBlocks[parseInt(idx)] || '');
|
||||
text = text.replace(/\x00INLINE(\d+)\x00/g, (_m: string, idx: string) => inlineCodes[parseInt(idx)] || '');
|
||||
text = text.replace(/\x00TABLE(\d+)\x00/g, (_m: string, idx: string) => tableBlocks[parseInt(idx)] || '');
|
||||
|
||||
// 11. Wrap in paragraph and clean up
|
||||
text = '<p class="md-p">' + text + '</p>';
|
||||
@@ -215,6 +252,7 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => {
|
||||
text = text.replace(/<p class="md-p">(<ul[\s\S]*?<\/ul>)<\/p>/g, '$1');
|
||||
text = text.replace(/<p class="md-p">(<hr[\s\S]*?)<\/p>/g, '$1');
|
||||
text = text.replace(/<p class="md-p">(<blockquote[\s\S]*?<\/blockquote>)<\/p>/g, '$1');
|
||||
text = text.replace(/<p class="md-p">(<table[\s\S]*?<\/table>)<\/p>/g, '$1');
|
||||
|
||||
// 12. 拆分为文本片段与媒体组件
|
||||
const parts = text.split(/(\x00MEDIA\d+\x00)/g);
|
||||
|
||||
Reference in new issue
Block a user