diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx
index bc85e60..80710df 100644
--- a/frontend/src/components/DataGrid.tsx
+++ b/frontend/src/components/DataGrid.tsx
@@ -673,11 +673,20 @@ const ContextMenuRow = React.memo(({ children, record, ...props }: any) => {
{ key: 'csv', label: '复制为 CSV', icon: , onClick: () => handleCopyCsv(record) },
{ key: 'copy', label: '复制为 Markdown', icon: , onClick: () => {
const records = getTargets();
- const lines = records.map((r: any) => {
- const { [GONAVI_ROW_KEY]: _rowKey, ...vals } = r;
- return `| ${Object.values(vals).join(' | ')} |`;
+ const orderedCols = displayDataRef.current.length > 0
+ ? Object.keys(displayDataRef.current[0]).filter(c => c !== GONAVI_ROW_KEY)
+ : [];
+ const header = `| ${orderedCols.join(' | ')} |`;
+ const separator = `| ${orderedCols.map(() => '---').join(' | ')} |`;
+ const rows = records.map((r: any) => {
+ const values = orderedCols.map(c => {
+ const v = r[c];
+ if (v === null || v === undefined) return 'NULL';
+ return String(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
+ });
+ return `| ${values.join(' | ')} |`;
});
- copyToClipboard(lines.join('\n'));
+ copyToClipboard([header, separator, ...rows].join('\n'));
} },
{ type: 'divider' },
{
@@ -3324,14 +3333,19 @@ const DataGrid: React.FC = ({
return;
}
const records = getTargets(record);
+ // 使用 columnNames 保持表定义的字段顺序,而非 Object.keys() 的不确定顺序
+ const orderedCols = columnNames.filter(c => c !== GONAVI_ROW_KEY);
const sqlList = records.map((r: any) => {
- const { [GONAVI_ROW_KEY]: _rowKey, ...vals } = r;
- const cols = Object.keys(vals);
- const values = Object.values(vals).map(v => v === null ? 'NULL' : `'${v}'`);
+ const values = orderedCols.map(c => {
+ const v = r[c];
+ if (v === null || v === undefined) return 'NULL';
+ const escaped = String(v).replace(/'/g, "''");
+ return `'${escaped}'`;
+ });
const targetTable = tableName || 'table';
- return `INSERT INTO \`${targetTable}\` (${cols.map(c => `\`${c}\``).join(', ')}) VALUES (${values.join(', ')});`;
+ return `INSERT INTO \`${targetTable}\` (${orderedCols.map(c => `\`${c}\``).join(', ')}) VALUES (${values.join(', ')});`;
});
- copyToClipboard(sqlList.join('\n')); }, [supportsCopyInsert, tableName, getTargets, copyToClipboard]);
+ copyToClipboard(sqlList.join('\n')); }, [supportsCopyInsert, tableName, columnNames, getTargets, copyToClipboard]);
const handleCopyJson = useCallback((record: any) => {
const records = getTargets(record);
@@ -3344,13 +3358,21 @@ const DataGrid: React.FC = ({
const handleCopyCsv = useCallback((record: any) => {
const records = getTargets(record);
+ // 使用 columnNames 保持表定义的字段顺序
+ const orderedCols = columnNames.filter(c => c !== GONAVI_ROW_KEY);
+ const header = orderedCols.map(c => `"${c}"`).join(',');
const lines = records.map((r: any) => {
- const { [GONAVI_ROW_KEY]: _rowKey, ...vals } = r;
- const values = Object.values(vals).map(v => v === null ? 'NULL' : `"${v}"`);
+ const values = orderedCols.map(c => {
+ const v = r[c];
+ if (v === null || v === undefined) return 'NULL';
+ // CSV 标准:值中的双引号转义为两个双引号
+ const escaped = String(v).replace(/"/g, '""');
+ return `"${escaped}"`;
+ });
return values.join(',');
});
- copyToClipboard(lines.join('\n'));
- }, [getTargets, copyToClipboard]);
+ copyToClipboard([header, ...lines].join('\n'));
+ }, [getTargets, columnNames, copyToClipboard]);
const buildConnConfig = useCallback(() => {
if (!connectionId) return null;