perf: workflow node past;feat: table export (#6250)

* perf: workflow node past

* feat: table export

* feat: table export
This commit is contained in:
Archer
2026-01-12 21:07:44 +08:00
committed by GitHub
parent 6ec16cf0cf
commit fea1c4ed14
9 changed files with 212 additions and 41 deletions

View File

@@ -0,0 +1,53 @@
import React, { useRef, useCallback } from 'react';
import { Box, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { exportTableToCSV } from './utils';
import MyIcon from '../Icon';
type MarkdownTableProps = {
children: React.ReactNode;
};
/**
* Custom table component for Markdown with CSV export functionality
*/
const MarkdownTable = ({ children }: MarkdownTableProps) => {
const { t } = useTranslation();
const tableRef = useRef<HTMLTableElement>(null);
const handleExport = useCallback(() => {
if (tableRef.current) {
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
exportTableToCSV(tableRef.current, `table-${timestamp}`);
}
}, []);
return (
<Box
ref={tableRef}
as="table"
_hover={{
'.export-button': {
display: 'flex'
}
}}
>
<IconButton
className="export-button"
icon={<MyIcon name="export" w={'14px'} />}
size={'xs'}
display="none"
variant={'whiteBase'}
onClick={handleExport}
position="absolute"
top={1}
right={2}
zIndex={1}
aria-label={''}
/>
{children}
</Box>
);
};
export default React.memo(MarkdownTable);

View File

@@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm';
import RehypeExternalLinks from 'rehype-external-links';
import { Box, Code as ChakraCode, Image, Link } from '@chakra-ui/react';
import MarkdownTable from './MarkdownTable';
type MarkdownProps = {
source: string;
@@ -114,13 +115,7 @@ const Markdown = ({ source, className }: MarkdownProps) => {
// 水平线
hr: () => <Box as="hr" my={4} borderColor="myGray.200" />,
// 表格
table: ({ children }: any) => (
<Box overflowX="auto" my={2}>
<Box as="table" w="100%" border="1px solid" borderColor="myGray.200" borderRadius="md">
{children}
</Box>
</Box>
),
table: MarkdownTable as any,
thead: ({ children }: any) => (
<Box as="thead" bg="myGray.50">
{children}

View File

@@ -0,0 +1,75 @@
/**
* Export table data to CSV format
* @param tableElement - HTML table element to export
* @param filename - Name of the exported file (without extension)
*/
export const exportTableToCSV = (tableElement: HTMLTableElement, filename: string = 'table') => {
const rows: string[][] = [];
// Extract header rows
const thead = tableElement.querySelector('thead');
if (thead) {
const headerRows = thead.querySelectorAll('tr');
headerRows.forEach((row) => {
const cells: string[] = [];
row.querySelectorAll('th').forEach((cell) => {
cells.push(escapeCsvCell(cell.textContent || ''));
});
if (cells.length > 0) {
rows.push(cells);
}
});
}
// Extract body rows
const tbody = tableElement.querySelector('tbody');
if (tbody) {
const bodyRows = tbody.querySelectorAll('tr');
bodyRows.forEach((row) => {
const cells: string[] = [];
row.querySelectorAll('td').forEach((cell) => {
cells.push(escapeCsvCell(cell.textContent || ''));
});
if (cells.length > 0) {
rows.push(cells);
}
});
}
// Convert to CSV format
const csvContent = rows.map((row) => row.join(',')).join('\n');
// Add BOM for Excel UTF-8 compatibility
const BOM = '\uFEFF';
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
// Trigger download
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `${filename}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
/**
* Escape special characters in CSV cell content
* @param cell - Cell content to escape
* @returns Escaped cell content
*/
const escapeCsvCell = (cell: string): string => {
// Remove leading/trailing whitespace
let content = cell.trim();
// If cell contains comma, double quote, or newline, wrap in quotes
if (content.includes(',') || content.includes('"') || content.includes('\n')) {
// Escape existing double quotes by doubling them
content = content.replace(/"/g, '""');
content = `"${content}"`;
}
return content;
};