fix(ui): handle single-line markdown tables and pipe-delimited tables

- parseMarkdownTable now handles tables stored as single line by
  counting columns from separator row and grouping cells accordingly
- Added isPipeTable detection for pipe-delimited data without header
  (e.g., "1 | name | qty\n2 | name | qty")
- Added renderPipeTable for headerless pipe tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 19:30:44 +08:00
parent d0124b0121
commit 93b8673363
+72 -11
View File
@@ -343,9 +343,11 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
const textRef = useRef<HTMLDivElement>(null);
const isTabTable = content.includes('\t') && content.includes('\n');
// 检测 markdown 表格:至少有 | 分隔符和分隔行 |---|
// 检测 markdown 表格:有 |---| 分隔行,或 pipe 分隔 + 换行(无表头的 pipe 表格)
const isMdTable = content.includes('|') && /\|[-\s:]+\|/.test(content);
const isTableLike = isTabTable || isMdTable;
const isPipeTable = !isMdTable && content.includes('|') && content.includes('\n')
&& content.split('\n').filter(l => l.includes('|')).length >= 2;
const isTableLike = isTabTable || isMdTable || isPipeTable;
useEffect(() => {
const checkTextOverflow = () => {
@@ -359,6 +361,8 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
// 预渲染内容并缓存
if (isMdTable) {
setRenderedContent(renderMarkdownTable(content));
} else if (isPipeTable) {
setRenderedContent(renderPipeTable(content));
} else if (isTabTable) {
setRenderedContent(renderReactTable(content));
} else {
@@ -378,17 +382,41 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
return rows;
};
// 解析 markdown 表格
// 解析 markdown 表格(支持多行和单行格式)
const parseMarkdownTable = (text: string): string[][] => {
// 先尝试按换行分割
const lines = text.split('\n').filter(l => l.trim());
const rows: string[][] = [];
for (const line of lines) {
// 跳过分隔行 |---|---|
if (/^\s*\|[-\s:]+\|/.test(line)) continue;
const cells = line.split('|')
.map(c => c.trim())
.filter((_, i, arr) => i > 0 && i < arr.length); // 去掉首尾空元素
if (cells.length > 0) rows.push(cells);
// 多行格式:有多行且包含分隔行
if (lines.length > 2) {
const rows: string[][] = [];
for (const line of lines) {
if (/^\s*\|[-\s:]+\|/.test(line)) continue; // 跳过分隔行
const cells = line.split('|').map(c => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length);
if (cells.length > 0) rows.push(cells);
}
if (rows.length > 1) return rows;
}
// 单行格式:整个表格在一行内,通过分隔行 |---|---| 来拆分
const sepMatch = text.match(/\|[\s-:]+(?:\|[\s-:]+)+\|/);
if (!sepMatch) return [];
const colCount = (sepMatch[0].match(/---/g) || []).length;
if (colCount === 0) return [];
const sepIdx = text.indexOf(sepMatch[0]);
const headerPart = text.substring(0, sepIdx);
const bodyPart = text.substring(sepIdx + sepMatch[0].length);
// 解析 header
const headerCells = headerPart.split('|').map(c => c.trim()).filter(c => c);
// 解析 body:按 | 分割后每 colCount 个cell为一行
const bodyCells = bodyPart.split('|').map(c => c.trim()).filter(c => c);
const rows: string[][] = [headerCells];
for (let i = 0; i < bodyCells.length; i += colCount) {
const row = bodyCells.slice(i, i + colCount);
if (row.length > 0) rows.push(row);
}
return rows;
};
@@ -437,6 +465,39 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
}
};
// 渲染 pipe 分隔表格(无表头,如 "1 | 名称 | 项 | 1\n2 | ..."
const renderPipeTable = (text: string) => {
try {
const rows = text.split('\n')
.filter(l => l.trim() && l.includes('|'))
.map(line => line.split('|').map(c => c.trim()));
if (rows.length === 0) return <div>{content}</div>;
return (
<div className="overflow-x-auto">
<table className="min-w-full border-collapse border border-gray-300">
<tbody>
{rows.map((row, rowIndex) => (
<tr key={`row-${rowIndex}`}>
{row.map((cell, cellIndex) => (
<td
key={`cell-${rowIndex}-${cellIndex}`}
className="px-2 py-1 border border-gray-300 text-xs text-left"
>
{cell || ' '}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
} catch (error) {
console.error('Pipe表格渲染错误:', error);
return <div>{content}</div>;
}
};
// 渲染React表格(tab分隔)
const renderReactTable = (text: string) => {
try {