From 12040be8682bce20379ce04d88db091cc4736161 Mon Sep 17 00:00:00 2001 From: 22605 <22605@utpcb.com> Date: Thu, 19 Mar 2026 11:24:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(markdown):=20=E6=94=AF=E6=8C=81=20Markdown?= =?UTF-8?q?=20=E8=A1=A8=E6=A0=BC=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 markdown.js 中添加表格检测和渲染逻辑 - 支持标准格式 (| cell |) 和简化格式 (cell | cell) - 添加 renderTable() 辅助函数处理表格解析 - 在 chat.css 中添加美观的表格样式 - 表头高亮、斑马纹、悬停效果 --- src/lib/markdown.js | 94 +++++++++++++++++++++++++++++++++++++++++++++ src/style/chat.css | 28 ++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/lib/markdown.js b/src/lib/markdown.js index 25e1cb0..09eaa38 100644 --- a/src/lib/markdown.js +++ b/src/lib/markdown.js @@ -89,6 +89,8 @@ export function renderMarkdown(text) { const result = [] let inList = false let listType = '' + let inTable = false + let tableRows = [] for (let i = 0; i < lines.length; i++) { let line = lines[i] @@ -100,6 +102,38 @@ export function renderMarkdown(text) { continue } + // 表格检测:表头分隔行 (|---|...|) + const isTableSeparator = /^\s*\|[\s\-:|]+\|\s*$/.test(line) || + /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(line) + + // 检测是否可能是表格行 + const isTableRow = /^\s*\|.*\|\s*$/.test(line) || + /^\s*[^\|]+\s*\|\s*[^\|]+/.test(line) + + // 如果在表格中,继续收集行 + if (inTable) { + if (isTableRow && line.trim() !== '') { + tableRows.push(line) + continue + } else { + // 表格结束,渲染表格 + result.push(renderTable(tableRows)) + inTable = false + tableRows = [] + } + } + + // 检测表格开始:当前行是表格行,且下一行是分隔行 + if (!inTable && isTableRow && i + 1 < lines.length) { + const nextLine = lines[i + 1] + if (/^\s*\|[\s\-:|]+\|\s*$/.test(nextLine) || + /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(nextLine)) { + inTable = true + tableRows.push(line) + continue + } + } + // 标题 const headingMatch = line.match(/^(#{1,3})\s+(.+)$/) if (headingMatch) { @@ -138,9 +172,69 @@ export function renderMarkdown(text) { } if (inList) result.push(``) + // 处理剩余的表格 + if (inTable && tableRows.length > 0) { + result.push(renderTable(tableRows)) + } return result.join('\n') } +/** + * 渲染 Markdown 表格 + * @param {string[]} rows - 表格行数组 + * @returns {string} HTML 表格 + */ +function renderTable(rows) { + if (!rows || rows.length < 2) return '' + + const table = [''] + let isHeaderRow = true + let hasSeparator = false + + for (let i = 0; i < rows.length; i++) { + let row = rows[i].trim() + + // 跳过空行 + if (!row) continue + // 检测分隔行 (|---|...|) + const isSeparator = /^\s*\|[\s\-:|]+\|\s*$/.test(row) || + /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(row) + if (isSeparator) { + hasSeparator = true + continue + } + + // 解析单元格 + let cells = [] + if (row.startsWith('|') && row.endsWith('|')) { + // 标准格式: | cell1 | cell2 | + cells = row.slice(1, -1).split('|') + } else { + // 简化格式: cell1 | cell2 + cells = row.split('|') + } + // 清理单元格内容 + cells = cells.map(cell => inlineFormat(cell.trim())) + if (cells.length === 0) continue + + // 渲染行 + const tag = isHeaderRow && !hasSeparator && i === 0 ? 'th' : 'td' + table.push(' ') + cells.forEach(cell => { + table.push(` <${tag}>${cell}`) + }) + table.push(' ') + + // 第一行后切换到数据行(如果有分隔行) + if (hasSeparator && i === 0) { + isHeaderRow = false + } + } + + table.push('
') + return table.join('\n') +} + function inlineFormat(text) { return text .replace(/\*\*(.+?)\*\*/g, '$1') diff --git a/src/style/chat.css b/src/style/chat.css index 1817cdb..1ae6f4f 100644 --- a/src/style/chat.css +++ b/src/style/chat.css @@ -304,6 +304,34 @@ .hl-func { color: #61afef; } .hl-type { color: #e5c07b; } +/* Markdown 表格 */ +.msg-ai .msg-bubble table { + width: 100%; + border-collapse: collapse; + margin: 8px 0; + font-size: 13px; +} + +.msg-ai .msg-bubble th, +.msg-ai .msg-bubble td { + border: 1px solid var(--border); + padding: 8px 12px; + text-align: left; +} + +.msg-ai .msg-bubble th { + background: var(--bg-tertiary); + font-weight: 600; +} + +.msg-ai .msg-bubble tr:nth-child(even) { + background: var(--bg-hover); +} + +.msg-ai .msg-bubble tr:hover { + background: rgba(99, 102, 241, 0.08); +} + /* 行内代码 */ .msg-ai .msg-bubble > code { background: var(--bg-hover, rgba(255,255,255,0.08));