feat(table): 表格下载支持 CSV 和 Markdown 双格式下拉选择
重构下载功能,将单一下载按钮改为 DropdownMenu 下拉菜单,新增 CSV 导出和 escapeCsvCell 辅助函数,downloadTextFile 支持自定义 MIME 类型。
This commit is contained in:
parent
8e6c8c7424
commit
269408b66f
@ -12,6 +12,12 @@ import {
|
||||
MessageResponse,
|
||||
type MessageResponseProps,
|
||||
} from "@/components/ai-elements/message";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { streamdownPlugins } from "@/core/streamdown";
|
||||
import { CopyButton } from "@/components/workspace/copy-button";
|
||||
@ -58,9 +64,22 @@ function toMarkdownTable(data: TableData): string {
|
||||
return [headerLine, dividerLine, ...rowLines].join("\n");
|
||||
}
|
||||
|
||||
function downloadMarkdownFile(content: string, filename: string) {
|
||||
function escapeCsvCell(cell: string): string {
|
||||
const normalized = cell.replace(/\r\n/g, "\n");
|
||||
if (!/["\n,]/.test(normalized)) return normalized;
|
||||
return `"${normalized.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
function toCsvTable(data: TableData): string {
|
||||
if (data.headers.length === 0) return "";
|
||||
const headerLine = data.headers.map(escapeCsvCell).join(",");
|
||||
const rowLines = data.rows.map((row) => row.map(escapeCsvCell).join(","));
|
||||
return [headerLine, ...rowLines].join("\r\n");
|
||||
}
|
||||
|
||||
function downloadTextFile(content: string, filename: string, mimeType: string) {
|
||||
const blob = new Blob(["\uFEFF", content], {
|
||||
type: "text/markdown;charset=utf-8",
|
||||
type: mimeType,
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
@ -99,13 +118,23 @@ export function MarkdownTable({
|
||||
})();
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
const table = tableRef.current;
|
||||
if (!table) return;
|
||||
const data = parseTableData(table);
|
||||
if (!data) return;
|
||||
const csv = toCsvTable(data);
|
||||
if (!csv) return;
|
||||
downloadTextFile(csv, "table.csv", "text/csv;charset=utf-8");
|
||||
}, []);
|
||||
|
||||
const handleDownloadMarkdown = useCallback(() => {
|
||||
const table = tableRef.current;
|
||||
if (!table) return;
|
||||
const data = parseTableData(table);
|
||||
if (!data) return;
|
||||
const markdown = toMarkdownTable(data);
|
||||
if (!markdown) return;
|
||||
downloadMarkdownFile(markdown, "table.md");
|
||||
downloadTextFile(markdown, "table.md", "text/markdown;charset=utf-8");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -115,15 +144,24 @@ export function MarkdownTable({
|
||||
>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<CopyButton className="text-muted-foreground hover:bg-transparent hover:text-foreground cursor-pointer p-1 transition-all" clipboardData={clipboardData} />
|
||||
<DropdownMenu>
|
||||
<Tooltip content={downloadLabel}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className="h-[32px] w-[32px] text-muted-foreground hover:text-foreground cursor-pointer p-1 transition-all"
|
||||
onClick={handleDownload}
|
||||
type="button"
|
||||
>
|
||||
<DownloadIcon size={16} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="end" className="min-w-[140px] p-1">
|
||||
<DropdownMenuItem onSelect={handleDownload}>CSV</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={handleDownloadMarkdown}>
|
||||
Markdown
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table
|
||||
|
||||
Loading…
Reference in New Issue
Block a user