diff --git a/frontend/src/pages/front/ChatPage.css b/frontend/src/pages/front/ChatPage.css index 0ac13ea..0ce673f 100644 --- a/frontend/src/pages/front/ChatPage.css +++ b/frontend/src/pages/front/ChatPage.css @@ -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); diff --git a/frontend/src/pages/front/ChatPage.tsx b/frontend/src/pages/front/ChatPage.tsx index 7bd0c20..337998f 100644 --- a/frontend/src/pages/front/ChatPage.tsx +++ b/frontend/src/pages/front/ChatPage.tsx @@ -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) => + `
' + text + '
'; @@ -215,6 +252,7 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => { text = text.replace(/(
(
(
)<\/p>/g, '$1'); + text = text.replace(/(
)<\/p>/g, '$1'); // 12. 拆分为文本片段与媒体组件 const parts = text.split(/(\x00MEDIA\d+\x00)/g);