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 && ( + <> +
+ + +