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:
@@ -343,9 +343,11 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const isTabTable = content.includes('\t') && content.includes('\n');
|
const isTabTable = content.includes('\t') && content.includes('\n');
|
||||||
// 检测 markdown 表格:至少有 | 分隔符和分隔行 |---|
|
// 检测 markdown 表格:有 |---| 分隔行,或 pipe 分隔 + 换行(无表头的 pipe 表格)
|
||||||
const isMdTable = content.includes('|') && /\|[-\s:]+\|/.test(content);
|
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(() => {
|
useEffect(() => {
|
||||||
const checkTextOverflow = () => {
|
const checkTextOverflow = () => {
|
||||||
@@ -359,6 +361,8 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
// 预渲染内容并缓存
|
// 预渲染内容并缓存
|
||||||
if (isMdTable) {
|
if (isMdTable) {
|
||||||
setRenderedContent(renderMarkdownTable(content));
|
setRenderedContent(renderMarkdownTable(content));
|
||||||
|
} else if (isPipeTable) {
|
||||||
|
setRenderedContent(renderPipeTable(content));
|
||||||
} else if (isTabTable) {
|
} else if (isTabTable) {
|
||||||
setRenderedContent(renderReactTable(content));
|
setRenderedContent(renderReactTable(content));
|
||||||
} else {
|
} else {
|
||||||
@@ -378,18 +382,42 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
return rows;
|
return rows;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 解析 markdown 表格
|
// 解析 markdown 表格(支持多行和单行格式)
|
||||||
const parseMarkdownTable = (text: string): string[][] => {
|
const parseMarkdownTable = (text: string): string[][] => {
|
||||||
|
// 先尝试按换行分割
|
||||||
const lines = text.split('\n').filter(l => l.trim());
|
const lines = text.split('\n').filter(l => l.trim());
|
||||||
|
|
||||||
|
// 多行格式:有多行且包含分隔行
|
||||||
|
if (lines.length > 2) {
|
||||||
const rows: string[][] = [];
|
const rows: string[][] = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// 跳过分隔行 |---|---|
|
if (/^\s*\|[-\s:]+\|/.test(line)) continue; // 跳过分隔行
|
||||||
if (/^\s*\|[-\s:]+\|/.test(line)) continue;
|
const cells = line.split('|').map(c => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length);
|
||||||
const cells = line.split('|')
|
|
||||||
.map(c => c.trim())
|
|
||||||
.filter((_, i, arr) => i > 0 && i < arr.length); // 去掉首尾空元素
|
|
||||||
if (cells.length > 0) rows.push(cells);
|
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;
|
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分隔)
|
// 渲染React表格(tab分隔)
|
||||||
const renderReactTable = (text: string) => {
|
const renderReactTable = (text: string) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user