mirror of
https://github.com/labring/FastGPT.git
synced 2026-02-27 01:02:22 +08:00
perf: workflow node past;feat: table export (#6250)
* perf: workflow node past * feat: table export * feat: table export
This commit is contained in:
53
packages/web/components/common/Markdown/MarkdownTable.tsx
Normal file
53
packages/web/components/common/Markdown/MarkdownTable.tsx
Normal 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);
|
||||
@@ -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}
|
||||
|
||||
75
packages/web/components/common/Markdown/utils.ts
Normal file
75
packages/web/components/common/Markdown/utils.ts
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user