From 1616ba8ae452ddfb24519854c175af862fbed0b2 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 6 May 2026 21:47:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(DataGrid):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=81=9A=E5=90=88=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=A4=8D=E5=88=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为查询结果页新增独立复制入口 - 支持 CSV、JSON、Markdown 复制当前结果集 - 补充聚合列复制与按钮可点击回归测试 --- .../src/components/DataGrid.layout.test.tsx | 20 +++++ frontend/src/components/DataGrid.tsx | 77 +++++++++++++++++ .../dataGridClipboardExport.test.ts | 40 +++++++++ .../src/components/dataGridClipboardExport.ts | 83 +++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 frontend/src/components/dataGridClipboardExport.test.ts create mode 100644 frontend/src/components/dataGridClipboardExport.ts diff --git a/frontend/src/components/DataGrid.layout.test.tsx b/frontend/src/components/DataGrid.layout.test.tsx index f9797d7..925354a 100644 --- a/frontend/src/components/DataGrid.layout.test.tsx +++ b/frontend/src/components/DataGrid.layout.test.tsx @@ -169,6 +169,26 @@ describe('DataGrid layout', () => { expect(markup).toContain('粘贴行'); }); + it('renders a clickable copy action for aggregate query results', () => { + const markup = renderToStaticMarkup( + , + ); + + expect(markup).toContain('data-grid-query-copy-action="true"'); + expect(markup).not.toMatch(/data-grid-query-copy-action="true"[^>]*disabled/); + expect(markup).toContain('复制'); + }); + it('renders a quick WHERE condition editor when table filters are visible', () => { const markup = renderToStaticMarkup( = ({ void message.success("Copied to clipboard"); }, []); + const getClipboardRows = useCallback(() => ( + pickRowsForClipboard({ + rows: mergedDisplayData as Array>, + selectedRowKeys, + columnNames: visibleColumnNames, + rowKeyField: GONAVI_ROW_KEY, + rowKeyToString: rowKeyStr, + }) + ), [mergedDisplayData, selectedRowKeys, visibleColumnNames, rowKeyStr]); + + const getClipboardColumnNames = useCallback((rows: Array>) => { + if (rows.length === 0) return []; + return visibleColumnNames.filter((columnName) => columnName !== GONAVI_ROW_KEY); + }, [visibleColumnNames]); + + const handleCopyQueryResultCsv = useCallback(() => { + const rows = getClipboardRows(); + const columns = getClipboardColumnNames(rows); + const text = buildClipboardCsv(rows, columns); + if (!text) { + void message.info('当前结果集没有可复制内容'); + return; + } + copyToClipboard(text); + }, [copyToClipboard, getClipboardColumnNames, getClipboardRows]); + + const handleCopyQueryResultJson = useCallback(() => { + const rows = getClipboardRows(); + const text = buildClipboardJson(rows); + if (!text) { + void message.info('当前结果集没有可复制内容'); + return; + } + copyToClipboard(text); + }, [copyToClipboard, getClipboardRows]); + + const handleCopyQueryResultMarkdown = useCallback(() => { + const rows = getClipboardRows(); + const columns = getClipboardColumnNames(rows); + const text = buildClipboardMarkdown(rows, columns); + if (!text) { + void message.info('当前结果集没有可复制内容'); + return; + } + copyToClipboard(text); + }, [copyToClipboard, getClipboardColumnNames, getClipboardRows]); + const handleOpenTableDdl = useCallback(async () => { if (!canViewDdl || !currentConnConfig || !tableName) { void message.error('当前表缺少连接或表名,无法查看 DDL'); @@ -4597,6 +4650,13 @@ const DataGrid: React.FC = ({ { key: 'html', label: 'HTML', onClick: () => handleExport('html') }, ]; + const queryResultCopyMenu: MenuProps['items'] = [ + { key: 'csv', label: 'CSV', onClick: handleCopyQueryResultCsv }, + { key: 'json', label: 'JSON', onClick: handleCopyQueryResultJson }, + { key: 'markdown', label: 'Markdown', onClick: handleCopyQueryResultMarkdown }, + ]; + const canCopyQueryResult = isQueryResultExport && mergedDisplayData.length > 0 && visibleColumnNames.length > 0; + const columnInfoSettingContent = (
显示设置
@@ -5434,6 +5494,23 @@ const DataGrid: React.FC = ({ )} + {isQueryResultExport && ( + <> +
+ + +