feat(mongodb): support fine-grained export for rows, columns, and cell regions

This commit is contained in:
Loc Nguyen
2026-10-08 11:25:37 +08:00
committed by GitHub
parent 74960d9fc8
commit d43ba11164
16 changed files with 907 additions and 93 deletions
+310 -14
View File
@@ -6276,6 +6276,10 @@ function affectedRowIds(): number[] {
if (hasRowSelection.value && selectedRowCount.value > 0) {
return [...selectedRowIds.value].filter((rowId) => !getRowItem(rowId)?.isDraft);
}
if (contextCell.value?.col === -1 && contextCell.value.rowId !== undefined) {
const item = getRowItem(contextCell.value.rowId);
return item && !item.isDraft ? [item.id] : [];
}
const range = selectedRange.value;
if (range && range.startRow !== range.endRow) {
return displayRowRefs.value
@@ -6286,6 +6290,41 @@ function affectedRowIds(): number[] {
return [];
}
function targetColumnIndexes(): number[] {
if (hasColumnSelection.value && selectedColumnIndexes.value.size > 0) {
return [...selectedColumnIndexes.value].sort((a, b) => a - b);
}
if (contextHeaderVisibleColIdx.value !== null && contextHeaderVisibleColIdx.value !== undefined) {
return [contextHeaderVisibleColIdx.value];
}
if (contextHeaderColumnIndex.value !== null) {
const idx = visibleColumnIndexes.value.indexOf(contextHeaderColumnIndex.value);
if (idx >= 0) return [idx];
}
return [];
}
const hasSelectedRows = computed(() => affectedRowIds().length > 0);
const hasSelectedColumns = computed(() => targetColumnIndexes().length > 0);
function activeCellSelectionTarget(): { rowIds: number[]; columnIndexes: number[] } | null {
if (selectedCellMatrix.value && selectedCellMatrix.value.rowIndexes.length > 0 && selectedCellMatrix.value.columnIndexes.length > 0) {
const rowIds = selectedCellMatrix.value.rowIndexes
.map((rowIndex) => displayItemAt(rowIndex))
.filter((item): item is RowItem => !!item && !item.isDraft)
.map((item) => item.id);
if (rowIds.length > 0) {
return {
rowIds,
columnIndexes: selectedCellMatrix.value.columnIndexes.slice(),
};
}
}
return null;
}
const hasSelectedCells = computed(() => !hasRowSelection.value && !hasColumnSelection.value && hasCellSelection.value && !!activeCellSelectionTarget());
function deletableRowIds(rowIds: number[]): number[] {
const eligible = rowIds.filter((rowId) => canDeleteRowItem(getRowItem(rowId)));
// A custom row-safety predicate is an all-or-nothing contract. Never silently
@@ -6297,55 +6336,170 @@ function deletableRowIds(rowIds: number[]): number[] {
function exportSelectedRowsCsv() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportCsv(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportCsv({ rowIds, columnIndexes });
}
function exportSelectedRowsXlsx() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportXlsx(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportXlsx({ rowIds, columnIndexes });
}
function openSelectedRowsXlsx() {
const cellTarget = activeCellSelectionTarget();
if (cellTarget) return openXlsx(cellTarget);
const rowIds = affectedRowIds();
if (rowIds.length === 0) return openXlsx();
return openXlsx(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
if (rowIds.length === 0 && columnIndexes === undefined) return openXlsx();
return openXlsx({ rowIds: rowIds.length > 0 ? rowIds : undefined, columnIndexes });
}
function exportSelectedRowsXlsxWithSql() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportXlsxWithSql(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportXlsxWithSql({ rowIds, columnIndexes });
}
function exportSelectedRowsJson() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportJson(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportJson({ rowIds, columnIndexes });
}
function exportSelectedRowsMarkdown() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportMarkdown(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportMarkdown({ rowIds, columnIndexes });
}
function exportSelectedRowsHtml() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportHtml(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportHtml({ rowIds, columnIndexes });
}
function exportSelectedRowsSql() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportSql(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportSql({ rowIds, columnIndexes });
}
function exportSelectedRowsTxt() {
const rowIds = affectedRowIds();
if (rowIds.length === 0) return;
return exportTxt(rowIds);
const columnIndexes = hasSelectedColumns.value ? targetColumnIndexes() : undefined;
return exportTxt({ rowIds, columnIndexes });
}
function exportSelectedColumnsCsv() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportCsv({ rowIds, columnIndexes });
}
function exportSelectedColumnsXlsx() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportXlsx({ rowIds, columnIndexes });
}
function exportSelectedColumnsXlsxWithSql() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportXlsxWithSql({ rowIds, columnIndexes });
}
function exportSelectedColumnsJson() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportJson({ rowIds, columnIndexes });
}
function exportSelectedColumnsMarkdown() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportMarkdown({ rowIds, columnIndexes });
}
function exportSelectedColumnsHtml() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportHtml({ rowIds, columnIndexes });
}
function exportSelectedColumnsSql() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportSql({ rowIds, columnIndexes });
}
function exportSelectedColumnsTxt() {
const columnIndexes = targetColumnIndexes();
if (columnIndexes.length === 0) return;
const rowIds = hasSelectedRows.value ? affectedRowIds() : undefined;
return exportTxt({ rowIds, columnIndexes });
}
function exportSelectedCellsCsv() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportCsv(target);
}
function exportSelectedCellsXlsx() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportXlsx(target);
}
function exportSelectedCellsXlsxWithSql() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportXlsxWithSql(target);
}
function exportSelectedCellsJson() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportJson(target);
}
function exportSelectedCellsMarkdown() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportMarkdown(target);
}
function exportSelectedCellsHtml() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportHtml(target);
}
function exportSelectedCellsSql() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportSql(target);
}
function exportSelectedCellsTxt() {
const target = activeCellSelectionTarget();
if (!target) return;
return exportTxt(target);
}
function executePreviewAction(action: PreviewAction) {
@@ -8552,7 +8706,7 @@ const exportMenuItems = computed(() => {
: []),
]
: [];
const selectedItems = isMultiRow.value
const selectedRowsItems = hasSelectedRows.value
? [
{
value: "selected-csv",
@@ -8581,6 +8735,64 @@ const exportMenuItems = computed(() => {
{ value: "selected-txt", label: t("grid.exportSelectedRowsTxt") },
]
: [];
const selectedColumnsItems = hasSelectedColumns.value
? [
{
value: "selected-columns-csv",
label: t("grid.exportSelectedColumnsCsv"),
separatorBefore: true,
},
{ value: "selected-columns-xlsx", label: t("grid.exportSelectedColumnsXlsx") },
...(canIncludeSql
? [
{
value: "selected-columns-xlsx-with-sql",
label: t("grid.exportSelectedColumnsXlsxWithSql"),
},
]
: []),
{ value: "selected-columns-json", label: t("grid.exportSelectedColumnsJson") },
{
value: "selected-columns-markdown",
label: t("grid.exportSelectedColumnsMarkdown"),
},
{
value: "selected-columns-html",
label: t("grid.exportSelectedColumnsHtml"),
},
{ value: "selected-columns-sql", label: t("grid.exportSelectedColumnsSql") },
{ value: "selected-columns-txt", label: t("grid.exportSelectedColumnsTxt") },
]
: [];
const selectedCellsItems = hasSelectedCells.value
? [
{
value: "selected-cells-csv",
label: t("grid.exportSelectedCellsCsv"),
separatorBefore: true,
},
{ value: "selected-cells-xlsx", label: t("grid.exportSelectedCellsXlsx") },
...(canIncludeSql
? [
{
value: "selected-cells-xlsx-with-sql",
label: t("grid.exportSelectedCellsXlsxWithSql"),
},
]
: []),
{ value: "selected-cells-json", label: t("grid.exportSelectedCellsJson") },
{
value: "selected-cells-markdown",
label: t("grid.exportSelectedCellsMarkdown"),
},
{
value: "selected-cells-html",
label: t("grid.exportSelectedCellsHtml"),
},
{ value: "selected-cells-sql", label: t("grid.exportSelectedCellsSql") },
{ value: "selected-cells-txt", label: t("grid.exportSelectedCellsTxt") },
]
: [];
if (!hasFullResultExport) {
return [
@@ -8593,7 +8805,9 @@ const exportMenuItems = computed(() => {
{ value: "sql", label: t("grid.exportSql") },
{ value: "txt", label: t("grid.exportTxt") },
...allResultItems,
...selectedItems,
...selectedRowsItems,
...selectedColumnsItems,
...selectedCellsItems,
];
}
@@ -8633,7 +8847,9 @@ const exportMenuItems = computed(() => {
{ value: "sql", label: t("grid.exportCurrentResultSql") },
{ value: "txt", label: t("grid.exportCurrentResultTxt") },
...allResultItems,
...selectedItems,
...selectedRowsItems,
...selectedColumnsItems,
...selectedCellsItems,
];
});
@@ -8669,6 +8885,22 @@ function selectExportMenuItem(value: string) {
"selected-html": exportSelectedRowsHtml,
"selected-sql": exportSelectedRowsSql,
"selected-txt": exportSelectedRowsTxt,
"selected-columns-csv": exportSelectedColumnsCsv,
"selected-columns-xlsx": exportSelectedColumnsXlsx,
"selected-columns-xlsx-with-sql": exportSelectedColumnsXlsxWithSql,
"selected-columns-json": exportSelectedColumnsJson,
"selected-columns-markdown": exportSelectedColumnsMarkdown,
"selected-columns-html": exportSelectedColumnsHtml,
"selected-columns-sql": exportSelectedColumnsSql,
"selected-columns-txt": exportSelectedColumnsTxt,
"selected-cells-csv": exportSelectedCellsCsv,
"selected-cells-xlsx": exportSelectedCellsXlsx,
"selected-cells-xlsx-with-sql": exportSelectedCellsXlsxWithSql,
"selected-cells-json": exportSelectedCellsJson,
"selected-cells-markdown": exportSelectedCellsMarkdown,
"selected-cells-html": exportSelectedCellsHtml,
"selected-cells-sql": exportSelectedCellsSql,
"selected-cells-txt": exportSelectedCellsTxt,
};
actions[value]?.();
}
@@ -12736,7 +12968,7 @@ function exportSubmenu(): ContextMenuItem {
action: exportXlsxWithSql,
});
}
if (isMultiRow.value) {
if (hasSelectedRows.value) {
items.push(
{ label: "", separator: true },
{ label: t("grid.exportSelectedRowsCsv"), action: exportSelectedRowsCsv },
@@ -12768,6 +13000,70 @@ function exportSubmenu(): ContextMenuItem {
{ label: t("grid.exportSelectedRowsTxt"), action: exportSelectedRowsTxt },
);
}
if (hasSelectedColumns.value) {
items.push(
{ label: "", separator: true },
{ label: t("grid.exportSelectedColumnsCsv"), action: exportSelectedColumnsCsv },
{
label: t("grid.exportSelectedColumnsXlsx"),
action: exportSelectedColumnsXlsx,
},
...(props.context === "results" && !!(props.exportSql || props.sql)?.trim()
? [
{
label: t("grid.exportSelectedColumnsXlsxWithSql"),
action: exportSelectedColumnsXlsxWithSql,
},
]
: []),
{
label: t("grid.exportSelectedColumnsJson"),
action: exportSelectedColumnsJson,
},
{
label: t("grid.exportSelectedColumnsMarkdown"),
action: exportSelectedColumnsMarkdown,
},
{
label: t("grid.exportSelectedColumnsHtml"),
action: exportSelectedColumnsHtml,
},
{ label: t("grid.exportSelectedColumnsSql"), action: exportSelectedColumnsSql },
{ label: t("grid.exportSelectedColumnsTxt"), action: exportSelectedColumnsTxt },
);
}
if (hasSelectedCells.value) {
items.push(
{ label: "", separator: true },
{ label: t("grid.exportSelectedCellsCsv"), action: exportSelectedCellsCsv },
{
label: t("grid.exportSelectedCellsXlsx"),
action: exportSelectedCellsXlsx,
},
...(props.context === "results" && !!(props.exportSql || props.sql)?.trim()
? [
{
label: t("grid.exportSelectedCellsXlsxWithSql"),
action: exportSelectedCellsXlsxWithSql,
},
]
: []),
{
label: t("grid.exportSelectedCellsJson"),
action: exportSelectedCellsJson,
},
{
label: t("grid.exportSelectedCellsMarkdown"),
action: exportSelectedCellsMarkdown,
},
{
label: t("grid.exportSelectedCellsHtml"),
action: exportSelectedCellsHtml,
},
{ label: t("grid.exportSelectedCellsSql"), action: exportSelectedCellsSql },
{ label: t("grid.exportSelectedCellsTxt"), action: exportSelectedCellsTxt },
);
}
const extractorItems = buildExtractorContextItems("export");
if (extractorItems.length > 0) {
items.push({ label: "", separator: true }, ...extractorItems);
@@ -88,6 +88,7 @@ function createMongoExportState(options: {
hasCompleteLocalResult?: UseDataGridExportOptions["hasCompleteLocalResult"];
completeLocalResult?: UseDataGridExportOptions["completeLocalResult"];
externalCellValue?: UseDataGridExportOptions["externalCellValue"];
visibleColumnIndexes?: number[];
}) {
const items = options.items ?? [options.item];
const selectedRowIds = options.selectedRowIds ?? new Set<number>();
@@ -121,6 +122,7 @@ function createMongoExportState(options: {
hasCompleteLocalResult: options.hasCompleteLocalResult,
completeLocalResult: options.completeLocalResult,
externalCellValue: options.externalCellValue,
visibleColumnIndexes: options.visibleColumnIndexes ? computed(() => options.visibleColumnIndexes) : undefined,
};
return useDataGridExport(state);
}
@@ -1805,6 +1807,74 @@ describe("useDataGridExport prepared row statements", () => {
expect(exportQueryResultCsv).toHaveBeenLastCalledWith(expect.any(String), ["name"], [["Visible"], ["Other"]], expect.anything(), expect.anything());
});
it("exports duplicate-named columns by source position from the full result", async () => {
setActivePinia(createPinia());
const state = createMongoExportState({
columns: ["id", "id"],
item: { ...row(["1", "one"]), sourceIndex: 0 },
mongoDocuments: [{ id: 1 }],
visibleColumnIndexes: [0, 1],
fullExportResult: async () => ({
columns: ["id", "id"],
column_types: ["int", "varchar"],
rows: [
["1", "one"],
["2", "two"],
],
affected_rows: 2,
execution_time_ms: 1,
}),
});
await state.exportCsv();
expect(exportQueryResultCsv).toHaveBeenLastCalledWith(
expect.any(String),
["id", "id"],
[
["1", "one"],
["2", "two"],
],
expect.anything(),
expect.anything(),
);
});
it("exports duplicate-named columns by source position when the complete result is local", async () => {
setActivePinia(createPinia());
const completeLocalResult = {
columns: ["id", "id"],
column_types: ["int", "varchar"],
rows: [
["1", "one"],
["2", "two"],
],
affected_rows: 2,
execution_time_ms: 1,
};
const state = createMongoExportState({
columns: ["id", "id"],
item: { ...row(["1", "one"]), sourceIndex: 0 },
mongoDocuments: [{ id: 1 }],
visibleColumnIndexes: [0, 1],
hasCompleteLocalResult: computed(() => true),
completeLocalResult: computed(() => completeLocalResult),
});
await state.exportCsv();
expect(exportQueryResultCsv).toHaveBeenLastCalledWith(
expect.any(String),
["id", "id"],
[
["1", "one"],
["2", "two"],
],
expect.anything(),
expect.anything(),
);
});
it("passes the CSV NULL setting to the export call", async () => {
setActivePinia(createPinia());
const state = createMongoExportState({
@@ -1869,6 +1939,166 @@ describe("useDataGridExport prepared row statements", () => {
expect(exportQueryResultJson).toHaveBeenCalledWith(expect.any(String), columns, [[mongoCopyDocument._id, mongoCopyDocument.valueMap, mongoCopyDocument.createdTime]]);
});
it("preserves Mongo Extended JSON when exporting a single selected row", async () => {
const columns = ["_id", "valueMap", "createdTime"];
const doc1 = {
_id: { $oid: "6a9fb51db2f0c46b94002f26" },
valueMap: { field1: "A", count: { $numberLong: "1234567890123" } },
createdTime: { $date: "2026-09-08T07:11:25.458Z" },
};
const doc2 = {
_id: { $oid: "6a9fb51db2f0c46b94002f27" },
valueMap: { field1: "B", count: { $numberLong: "9876543210987" } },
createdTime: { $date: "2026-09-09T08:00:00.000Z" },
};
const item1 = { ...row(["6a9fb51db2f0c46b94002f26", JSON.stringify(doc1.valueMap), 'ISODate("2026-09-08T07:11:25.458Z")']), id: 101, sourceIndex: 0 };
const item2 = { ...row(["6a9fb51db2f0c46b94002f27", JSON.stringify(doc2.valueMap), 'ISODate("2026-09-09T08:00:00.000Z")']), id: 102, sourceIndex: 1 };
const state = createMongoExportState({
columns,
item: item1,
items: [item1, item2],
mongoDocuments: [doc1, doc2],
selectedRowIds: new Set([102]),
});
await state.exportJson([102]);
expect(exportQueryResultJson).toHaveBeenCalledWith(expect.any(String), columns, [[doc2._id, doc2.valueMap, doc2.createdTime]]);
});
it("preserves Mongo Extended JSON when exporting selected columns", async () => {
const columns = ["_id", "valueMap", "createdTime"];
const doc1 = {
_id: { $oid: "6a9fb51db2f0c46b94002f26" },
valueMap: { field1: "A" },
createdTime: { $date: "2026-09-08T07:11:25.458Z" },
};
const item1 = { ...row(["6a9fb51db2f0c46b94002f26", JSON.stringify(doc1.valueMap), 'ISODate("2026-09-08T07:11:25.458Z")']), id: 101, sourceIndex: 0 };
const state = createMongoExportState({
columns,
item: item1,
items: [item1],
mongoDocuments: [doc1],
});
await state.exportJson({ columnIndexes: [1, 2] });
expect(exportQueryResultJson).toHaveBeenCalledWith(expect.any(String), ["valueMap", "createdTime"], [[doc1.valueMap, doc1.createdTime]]);
});
it("preserves Mongo Extended JSON when exporting selected row and column intersection", async () => {
const columns = ["_id", "valueMap", "createdTime"];
const doc1 = {
_id: { $oid: "6a9fb51db2f0c46b94002f26" },
valueMap: { field1: "A" },
createdTime: { $date: "2026-09-08T07:11:25.458Z" },
};
const doc2 = {
_id: { $oid: "6a9fb51db2f0c46b94002f27" },
valueMap: { field1: "B" },
createdTime: { $date: "2026-09-09T08:00:00.000Z" },
};
const item1 = { ...row(["6a9fb51db2f0c46b94002f26", JSON.stringify(doc1.valueMap), 'ISODate("2026-09-08T07:11:25.458Z")']), id: 101, sourceIndex: 0 };
const item2 = { ...row(["6a9fb51db2f0c46b94002f27", JSON.stringify(doc2.valueMap), 'ISODate("2026-09-09T08:00:00.000Z")']), id: 102, sourceIndex: 1 };
const state = createMongoExportState({
columns,
item: item1,
items: [item1, item2],
mongoDocuments: [doc1, doc2],
});
await state.exportJson({ rowIds: [102], columnIndexes: [0, 2] });
expect(exportQueryResultJson).toHaveBeenCalledWith(expect.any(String), ["_id", "createdTime"], [[doc2._id, doc2.createdTime]]);
});
it("exports non-Mongo CSV with selected columns and rows", async () => {
setActivePinia(createPinia());
const table: DataGridTableMeta = {
tableName: "users",
primaryKeys: ["id"],
columns: [
{ name: "id", data_type: "int" },
{ name: "name", data_type: "varchar" },
{ name: "email", data_type: "varchar" },
],
};
const rowDataList = [
[1, "Alice", "alice@example.com"],
[2, "Bob", "bob@example.com"],
[3, "Charlie", "charlie@example.com"],
];
const state = createExportState(table, ["id", "name", "email"], undefined, undefined, undefined, rowDataList, [2]);
await state.exportCsv({ rowIds: [2], columnIndexes: [1, 2] });
expect(exportQueryResultCsv).toHaveBeenLastCalledWith(expect.any(String), ["name", "email"], [["Bob", "bob@example.com"]], expect.anything(), expect.anything());
});
it("exports non-Mongo JSON with selected columns and rows", async () => {
setActivePinia(createPinia());
const table: DataGridTableMeta = {
tableName: "users",
primaryKeys: ["id"],
columns: [
{ name: "id", data_type: "int" },
{ name: "name", data_type: "varchar" },
{ name: "email", data_type: "varchar" },
],
};
const rowDataList = [
[1, "Alice", "alice@example.com"],
[2, "Bob", "bob@example.com"],
[3, "Charlie", "charlie@example.com"],
];
const state = createExportState(table, ["id", "name", "email"], undefined, undefined, undefined, rowDataList, [1, 3]);
await state.exportJson({ rowIds: [1, 3], columnIndexes: [1] });
expect(exportQueryResultJson).toHaveBeenLastCalledWith(expect.any(String), ["name"], [["Alice"], ["Charlie"]]);
});
it("exports rectangular cell selection matrix with correct rows and columns", async () => {
setActivePinia(createPinia());
const table: DataGridTableMeta = {
tableName: "users",
primaryKeys: ["id"],
columns: [
{ name: "id", data_type: "int" },
{ name: "name", data_type: "varchar" },
{ name: "email", data_type: "varchar" },
],
};
const rowDataList = [
[1, "Alice", "alice@example.com"],
[2, "Bob", "bob@example.com"],
[3, "Charlie", "charlie@example.com"],
];
const matrix: CellSelectionMatrix = {
rowIndexes: [0, 1],
columnIndexes: [1, 2],
columns: ["name", "email"],
rows: [
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
],
};
const state = createExportState(table, ["id", "name", "email"], matrix, undefined, undefined, rowDataList);
await state.exportCsv({ rowIds: [1, 2], columnIndexes: [1, 2] });
expect(exportQueryResultCsv).toHaveBeenLastCalledWith(
expect.any(String),
["name", "email"],
[
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
],
expect.anything(),
expect.anything(),
);
});
});
// issue #7471:文本型 MySQL VARBINARY 复制单元格/多选/整行时,外部剪贴板应呈现原始字符串;
@@ -172,6 +172,21 @@ describe("SQL export column selection across entrypoints", () => {
expect(mocks.toast).toHaveBeenCalledWith("grid.exportFailed", 5000);
});
it("resolves subset type fallbacks by name instead of subset position", async () => {
const choices = sqlExportColumnChoices(["name"]);
vi.mocked(showSqlInsertModeDialog).mockResolvedValue({ insertMode: "single", selectedColumns: [choices[0]!] });
const state = useDataGridExport(
createOptions({
columns: computed(() => ["name"]),
allColumns: computed(() => ["id", "name"]),
allColumnTypes: computed(() => ["tsvector", "text"]),
tableMeta: computed(() => ({ tableName: "users", primaryKeys: [], columns: [] })),
}),
);
await state.exportSql({ columnIndexes: [0] });
expect(showSqlInsertModeDialog).toHaveBeenCalledWith({ allowSplit: false, columns: choices });
});
it("keeps legacy omitted selection behavior", async () => {
vi.mocked(showSqlInsertModeDialog).mockResolvedValue({ insertMode: "batch" });
await useDataGridExport(createOptions()).exportCurrentPageSql();
+160 -79
View File
@@ -58,6 +58,23 @@ const FORMAT_META: Record<string, { ext: string; label: string }> = {
txt: { ext: "txt", label: "Text" },
};
export interface DataGridExportTarget {
rowIds?: number[];
columnIndexes?: number[];
}
export type ExportTargetParam = number[] | DataGridExportTarget;
export function normalizeExportTarget(target?: ExportTargetParam, columnIndexesParam?: number[]): { rowIds?: number[]; columnIndexes?: number[] } {
if (Array.isArray(target)) {
return { rowIds: target, columnIndexes: columnIndexesParam };
}
return {
rowIds: target?.rowIds,
columnIndexes: columnIndexesParam ?? target?.columnIndexes,
};
}
interface RowItem {
id: number;
sourceIndex?: number;
@@ -346,10 +363,15 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
function normalizeCompleteLocalResult(result: QueryResult): { columns: string[]; columnTypes: string[]; columnComments: Array<string | undefined>; rows: CellValue[][]; mongoCopyDocuments?: unknown[]; spatialColumns?: QueryResult["spatial_columns"]; spatialValues?: QueryResult["spatial_values"] } {
function normalizeCompleteLocalResult(
result: QueryResult,
targetCols?: readonly string[],
sourceIndexesParam?: readonly number[],
): { columns: string[]; columnTypes: string[]; columnComments: Array<string | undefined>; rows: CellValue[][]; mongoCopyDocuments?: unknown[]; spatialColumns?: QueryResult["spatial_columns"]; spatialValues?: QueryResult["spatial_values"] } {
const editorSettings = useSettingsStore().editorSettings;
if (databaseType.value === "mongodb") {
const projected = projectResultColumns(result, columns.value);
const isSubset = targetCols !== undefined && (targetCols.length !== result.columns.length || !targetCols.every((col, i) => col === result.columns[i]));
if (databaseType.value === "mongodb" || isSubset) {
const projected = projectResultColumns(result, targetCols ?? columns.value, sourceIndexesParam);
const rows = editorSettings.exportRowLimitEnabled ? projected.rows.slice(0, editorSettings.exportRowLimit) : projected.rows;
return {
columns: projected.columns,
@@ -397,6 +419,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
function projectResultColumns(
result: QueryResult,
targetColumns: readonly string[],
sourceIndexesParam?: readonly number[],
): {
columns: string[];
columnTypes: string[];
@@ -404,7 +427,9 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
spatialColumns?: QueryResult["spatial_columns"];
spatialValues?: QueryResult["spatial_values"];
} {
const sourceIndexes = targetColumns.map((column) => result.columns.indexOf(column));
// Name lookup resolves duplicate column names (e.g. SELECT a.id, b.id) to
// the first occurrence; prefer the caller's explicit source indexes.
const sourceIndexes = sourceIndexesParam?.length === targetColumns.length ? sourceIndexesParam.map((index) => (index >= 0 && index < result.columns.length ? index : -1)) : targetColumns.map((column) => result.columns.indexOf(column));
const targetIndexBySource = new Map<number, number>();
sourceIndexes.forEach((sourceIndex, targetIndex) => {
if (sourceIndex >= 0) targetIndexBySource.set(sourceIndex, targetIndex);
@@ -421,25 +446,29 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
};
}
function mongoLocalRowsForJson(items: RowItem[]): CellValue[][] {
function mongoLocalRowsForJson(items: RowItem[], targetCols: readonly string[] = columns.value): CellValue[][] {
return items.map((item) => {
const document = rowToJsonObject(item);
return columns.value.map((column) => (document[column] as CellValue) ?? null);
return targetCols.map((column) => (document[column] as CellValue) ?? null);
});
}
function externalizeRows(rows: CellValue[][]): CellValue[][] {
function externalizeRows(rows: CellValue[][], columnIndexes?: number[]): CellValue[][] {
if (columnIndexes) {
return rows.map((row) => columnIndexes.map((colIdx) => (options.externalCellValue ? options.externalCellValue(row[colIdx], colIdx) : row[colIdx])));
}
if (!options.externalCellValue) return rows;
return rows.map((row) => row.map((value, columnIndex) => options.externalCellValue!(value, columnIndex)));
}
async function resultToExport(
rowIds?: number[],
target?: ExportTargetParam,
onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void,
useFullExport = true,
formatDateTime = true,
headerMode: XlsxHeaderMode = "name",
preserveMongoExtendedJson = false,
columnIndexesParam?: number[],
): Promise<{
columns: string[];
columnTypes: string[];
@@ -448,10 +477,20 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
spatialValues?: QueryResult["spatial_values"];
rows: CellValue[][];
}> {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
const hasColumnSubset = columnIndexes !== undefined;
const validColumnIndexes = hasColumnSubset ? columnIndexes.filter((index) => index >= 0 && index < columns.value.length) : columns.value.map((_, index) => index);
const targetColumns = validColumnIndexes.map((index) => columns.value[index]!);
const targetColumnTypes = (columnTypes.value ?? []).length === columns.value.length ? validColumnIndexes.map((index) => columnTypes.value?.[index] ?? "") : targetColumns.map(() => "");
const visibleSourceIndexes = visibleColumnIndexesOption?.value;
// Explicit source indexes are only meaningful when the grid maps visible
// columns to result positions; otherwise keep name-based resolution.
const projectionSourceIndexes = visibleSourceIndexes ? validColumnIndexes.map((targetIdx) => visibleSourceIndexes[targetIdx] ?? targetIdx) : undefined;
if (useFullExport && rowIds === undefined && fullExportResult && !hasCompleteLocalResult?.value) {
const result = await fullExportResult(onProgress);
if (result) {
const projected = databaseType.value === "mongodb" ? projectResultColumns(result, columns.value) : undefined;
const projected = databaseType.value === "mongodb" || hasColumnSubset ? projectResultColumns(result, targetColumns, projectionSourceIndexes) : undefined;
const exportedColumns = projected?.columns ?? result.columns;
const exportedColumnTypes = projected?.columnTypes ?? result.column_types ?? [];
const exportedRows = projected?.rows ?? result.rows;
@@ -480,7 +519,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
// client-side filters/search and unsaved edits, which would silently
// change what the export contains.
if (useFullExport && rowIds === undefined && hasCompleteLocalResult?.value && completeLocalResult?.value) {
const normalized = normalizeCompleteLocalResult(completeLocalResult.value);
const normalized = hasColumnSubset || databaseType.value === "mongodb" ? normalizeCompleteLocalResult(completeLocalResult.value, targetColumns, projectionSourceIndexes) : normalizeCompleteLocalResult(completeLocalResult.value);
const columnComments = buildXlsxHeaderOverrides(normalized.columns, normalized.columnComments, headerMode);
return {
...applyGlobalDateTimeExportFormat(
@@ -492,23 +531,40 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
spatialValues: normalized.spatialValues,
};
}
const commentHeader = buildXlsxHeaderOverrides(columns.value, visibleXlsxColumnComments.value, headerMode);
const targetComments = commentsForExportColumns(targetColumns, false);
const commentHeader = buildXlsxHeaderOverrides(targetColumns, targetComments, headerMode);
const exportItems = await resolveVisibleRowValues(rowsToExport(rowIds));
const visibleIndexes = visibleColumnIndexesOption?.value ?? columns.value.map((_, index) => index);
const spatialColumns = spatialColumnsOption?.value?.map((column) => ({ column_index: visibleIndexes.indexOf(column.column_index), srid: column.srid })).filter((column) => column.column_index >= 0);
const targetSourceIndexes = validColumnIndexes.map((targetIdx) => visibleIndexes[targetIdx] ?? targetIdx);
const targetIndexBySource = new Map<number, number>();
targetSourceIndexes.forEach((sourceIdx, targetIdx) => {
targetIndexBySource.set(sourceIdx, targetIdx);
});
const spatialColumns = spatialColumnsOption?.value?.flatMap((column) => {
const targetIndex = targetIndexBySource.get(column.column_index);
return targetIndex === undefined ? [] : [{ column_index: targetIndex, srid: column.srid }];
});
const spatialValues = spatialValuesOption?.value;
const mappedSpatialValues = spatialValues?.length ? exportItems.map((item) => targetSourceIndexes.map((sourceIdx) => spatialValues[item.sourceIndex ?? -1]?.[sourceIdx] ?? null)) : undefined;
return {
...applyGlobalDateTimeExportFormat(
{
columns: columns.value,
columnTypes: (columnTypes.value ?? []).map((type) => type ?? ""),
rows: preserveMongoExtendedJson && databaseType.value === "mongodb" ? mongoLocalRowsForJson(exportItems) : externalizeRows(exportItems.map((item) => item.data)),
columns: targetColumns,
columnTypes: targetColumnTypes,
rows:
preserveMongoExtendedJson && databaseType.value === "mongodb"
? mongoLocalRowsForJson(exportItems, targetColumns)
: externalizeRows(
exportItems.map((item) => item.data),
validColumnIndexes,
),
},
formatDateTime && !preserveMongoExtendedJson,
),
columnComments: commentHeader,
...(spatialColumns?.length ? { spatialColumns } : {}),
...(spatialValues?.length ? { spatialValues: exportItems.map((item) => visibleIndexes.map((index) => spatialValues[item.sourceIndex ?? -1]?.[index] ?? null)) } : {}),
...(mappedSpatialValues?.length ? { spatialValues: mappedSpatialValues } : {}),
};
}
@@ -869,11 +925,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return exported;
}
async function exportCsv(rowIds?: number[]) {
async function exportCsv(target?: ExportTargetParam, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
try {
if (await exportQueryResultViaBackend("csv", rowIds)) return;
if (await exportFullTableDataViaBackend("csv", rowIds)) return;
if (await exportQueryResultViaBackend("csv", rowIds, false, "name", true, undefined, false, columnIndexes)) return;
if (await exportFullTableDataViaBackend("csv", rowIds, "name", true, undefined, false, columnIndexes)) return;
const needsFullExport = rowIds === undefined && !!fullExportResult && !hasCompleteLocalResult?.value;
if (needsFullExport && exportProgressDialog && exportProgressState) {
@@ -891,7 +948,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
};
exportProgressDialog.value = true;
}
const result = await resultToExport(rowIds, (info) => {
const result = await resultToExport({ rowIds, columnIndexes }, (info) => {
if (needsFullExport && exportProgressState && exportProgressState.value.status === "Running") {
// Guard against the COUNT estimate being too low: if the real
// fetched count exceeds it, bump totalRows so the progress bar
@@ -951,7 +1008,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageCsv() {
async function exportCurrentPageCsv(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
try {
let outputPath = exportFileName("export-page", "csv", { page: true });
@@ -964,7 +1021,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(undefined, undefined, false);
const result = await resultToExport({ columnIndexes }, undefined, false);
await api.exportQueryResultCsv(outputPath, result.columns, result.rows, useSettingsStore().editorSettings.csvQuoteMode, csvNullLiteralForMode(useSettingsStore().editorSettings.csvNullMode));
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -973,11 +1030,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportJson(rowIds?: number[]) {
async function exportJson(target?: ExportTargetParam, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
try {
if (await exportFullTableDataViaBackend("json", rowIds)) return;
if (await exportQueryResultViaBackend("json", rowIds)) return;
if (await exportFullTableDataViaBackend("json", rowIds, "name", true, undefined, false, columnIndexes)) return;
if (await exportQueryResultViaBackend("json", rowIds, false, "name", true, undefined, false, columnIndexes)) return;
let outputPath = exportFileName("export", "json");
if (isTauriRuntime()) {
@@ -989,7 +1047,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(rowIds, undefined, true, true, "name", true);
const result = await resultToExport({ rowIds, columnIndexes }, undefined, true, true, "name", true);
await api.exportQueryResultJson(outputPath, result.columns, result.rows);
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -998,7 +1056,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageJson() {
async function exportCurrentPageJson(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
try {
let outputPath = exportFileName("export-page", "json", { page: true });
@@ -1011,7 +1069,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(undefined, undefined, false, true, "name", true);
const result = await resultToExport({ columnIndexes }, undefined, false, true, "name", true);
await api.exportQueryResultJson(outputPath, result.columns, result.rows);
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -1020,10 +1078,11 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportMarkdown(rowIds?: number[]) {
async function exportMarkdown(target?: ExportTargetParam, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
try {
if (await exportFullTableDataViaBackend("markdown", rowIds)) return;
if (await exportFullTableDataViaBackend("markdown", rowIds, "name", true, undefined, false, columnIndexes)) return;
let outputPath = exportFileName("export", "md");
if (isTauriRuntime()) {
@@ -1035,7 +1094,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(rowIds);
const result = await resultToExport({ rowIds, columnIndexes });
await api.exportQueryResultMarkdown(outputPath, result.columns, result.rows);
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -1044,7 +1103,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageMarkdown() {
async function exportCurrentPageMarkdown(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
try {
let outputPath = exportFileName("export-page", "md", { page: true });
@@ -1057,7 +1116,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(undefined, undefined, false);
const result = await resultToExport({ columnIndexes }, undefined, false);
await api.exportQueryResultMarkdown(outputPath, result.columns, result.rows);
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -1066,12 +1125,13 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportTxt(rowIds?: number[]) {
async function exportTxt(target?: ExportTargetParam, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
try {
if (await exportQueryResultViaBackend("txt", rowIds)) return;
if (await exportFullTableDataViaBackend("txt", rowIds)) return;
const result = await resultToExport(rowIds);
if (await exportQueryResultViaBackend("txt", rowIds, false, "name", true, undefined, false, columnIndexes)) return;
if (await exportFullTableDataViaBackend("txt", rowIds, "name", true, undefined, false, columnIndexes)) return;
const result = await resultToExport({ rowIds, columnIndexes });
const content = formatTsv(result.columns, result.rows);
const saved = await saveTextFile(content, exportFileName(tableMeta.value?.tableName || "export", "txt", { preferFallback: true }), "Text", "txt");
if (!saved) return;
@@ -1082,7 +1142,8 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportHtml(rowIds?: number[], openAfterExport = false) {
async function exportHtml(target?: ExportTargetParam, openAfterExport = false, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
try {
let outputPath = exportFileName("export", "html");
@@ -1098,7 +1159,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
outputPath = path;
}
}
const result = await resultToExport(rowIds);
const result = await resultToExport({ rowIds, columnIndexes });
await api.exportQueryResultHtml(outputPath, currentExportTitle(), result.columns, result.rows);
notifyExportSuccess(openAfterExport ? undefined : outputPath);
if (openAfterExport && isTauriRuntime()) await api.openQueryResultTempFile(outputPath);
@@ -1108,7 +1169,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageHtml() {
async function exportCurrentPageHtml(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
try {
let outputPath = exportFileName("export-page", "html", { page: true });
@@ -1121,7 +1182,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(undefined, undefined, false);
const result = await resultToExport({ columnIndexes }, undefined, false);
await api.exportQueryResultHtml(outputPath, currentExportTitle(), result.columns, result.rows);
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -1130,10 +1191,10 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageTxt() {
async function exportCurrentPageTxt(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
try {
const result = await resultToExport(undefined, undefined, false);
const result = await resultToExport({ columnIndexes }, undefined, false);
const content = formatTsv(result.columns, result.rows);
const saved = await saveTextFile(content, exportFileName("export-page", "txt", { page: true }), "Text", "txt");
if (!saved) return;
@@ -1144,14 +1205,15 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportXlsxResult(rowIds: number[] | undefined, includeSqlSheet: boolean, openAfterExport = false) {
async function exportXlsxResult(target?: ExportTargetParam, includeSqlSheet = false, openAfterExport = false, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
const exportOptions = await showXlsxHeaderDialog();
if (exportOptions === null) return;
await runExclusiveExport(async () => {
try {
if (await exportQueryResultViaBackend("xlsx", rowIds, includeSqlSheet, exportOptions.headerMode, exportOptions.autoFilter, undefined, openAfterExport)) return;
if (await exportFullTableDataViaBackend("xlsx", rowIds, exportOptions.headerMode, exportOptions.autoFilter, undefined, openAfterExport)) return;
if (await exportQueryResultViaBackend("xlsx", rowIds, includeSqlSheet, exportOptions.headerMode, exportOptions.autoFilter, undefined, openAfterExport, columnIndexes)) return;
if (await exportFullTableDataViaBackend("xlsx", rowIds, exportOptions.headerMode, exportOptions.autoFilter, undefined, openAfterExport, columnIndexes)) return;
let outputPath = exportFileName("export", "xlsx");
if (isTauriRuntime()) {
@@ -1183,7 +1245,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
exportProgressDialog.value = true;
}
const result = await resultToExport(
rowIds,
{ rowIds, columnIndexes },
(info) => {
if (needsFullExport && exportProgressState && exportProgressState.value.status === "Running") {
const adjustedTotal = info.totalRows !== null && info.rowsExported > info.totalRows ? info.rowsExported : info.totalRows;
@@ -1233,23 +1295,23 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportXlsx(rowIds?: number[]) {
await exportXlsxResult(rowIds, false);
async function exportXlsx(target?: ExportTargetParam, columnIndexes?: number[]) {
await exportXlsxResult(target, false, false, columnIndexes);
}
async function openXlsx(rowIds?: number[]) {
await exportXlsxResult(rowIds, false, true);
async function openXlsx(target?: ExportTargetParam, columnIndexes?: number[]) {
await exportXlsxResult(target, false, true, columnIndexes);
}
async function openBrowser(rowIds?: number[]) {
await exportHtml(rowIds, true);
async function openBrowser(target?: ExportTargetParam, columnIndexes?: number[]) {
await exportHtml(target, true, columnIndexes);
}
async function exportXlsxWithSql(rowIds?: number[]) {
await exportXlsxResult(rowIds, true);
async function exportXlsxWithSql(target?: ExportTargetParam, columnIndexes?: number[]) {
await exportXlsxResult(target, true, false, columnIndexes);
}
async function exportCurrentPageXlsxResult(includeSqlSheet: boolean) {
async function exportCurrentPageXlsxResult(includeSqlSheet: boolean, columnIndexes?: number[]) {
const exportOptions = await showXlsxHeaderDialog();
if (exportOptions === null) return;
@@ -1265,7 +1327,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
if (!path) return;
outputPath = path;
}
const result = await resultToExport(undefined, undefined, false, true, exportOptions.headerMode);
const result = await resultToExport({ columnIndexes }, undefined, false, true, exportOptions.headerMode);
await writeXlsxResult(outputPath, result, includeSqlSheet, exportOptions.autoFilter, currentPageExportSql());
notifyExportSuccess(outputPath);
} catch (e: any) {
@@ -1274,12 +1336,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageXlsx() {
await exportCurrentPageXlsxResult(false);
async function exportCurrentPageXlsx(columnIndexes?: number[]) {
await exportCurrentPageXlsxResult(false, columnIndexes);
}
async function exportCurrentPageXlsxWithSql() {
await exportCurrentPageXlsxResult(true);
async function exportCurrentPageXlsxWithSql(columnIndexes?: number[]) {
await exportCurrentPageXlsxResult(true, columnIndexes);
}
async function exportResultSheetsXlsxResult(sheetsToExport: Array<{ sheetName: string; result: QueryResult; sql?: string }> | undefined, includeSqlSheet: boolean) {
@@ -1360,11 +1422,11 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return excludeColumns ? { excludePrimaryKeys: true, primaryKeys: excludeColumns } : {};
}
async function exportFullTableDataViaBackend(format: "csv" | "xlsx" | "json" | "markdown" | "sql" | "txt", rowIds?: number[], headerMode: XlsxHeaderMode = "name", autoFilter = true, sqlExportOptions?: SqlExportOptions, openAfterExport = false): Promise<boolean> {
async function exportFullTableDataViaBackend(format: "csv" | "xlsx" | "json" | "markdown" | "sql" | "txt", rowIds?: number[], headerMode: XlsxHeaderMode = "name", autoFilter = true, sqlExportOptions?: SqlExportOptions, openAfterExport = false, columnIndexes?: number[]): Promise<boolean> {
const meta = tableMeta.value;
// The backend table exporter currently builds two-part table names. External
// Doris/StarRocks catalogs need the data-tab paginator's three-part SQL.
if (rowIds !== undefined || context.value !== "table-data" || !meta || meta.catalog || !connectionId.value || !database.value) {
if (rowIds !== undefined || columnIndexes !== undefined || context.value !== "table-data" || !meta || meta.catalog || !connectionId.value || !database.value) {
return false;
}
@@ -1474,8 +1536,17 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return true;
}
async function exportQueryResultViaBackend(format: "csv" | "xlsx" | "json" | "txt" | "sql", rowIds?: number[], includeSqlSheet = false, headerMode: XlsxHeaderMode = "name", autoFilter = true, sqlExportOptions?: SqlExportOptions, openAfterExport = false): Promise<boolean> {
if (rowIds !== undefined || context.value !== "results" || !queryResultExportRequest) {
async function exportQueryResultViaBackend(
format: "csv" | "xlsx" | "json" | "txt" | "sql",
rowIds?: number[],
includeSqlSheet = false,
headerMode: XlsxHeaderMode = "name",
autoFilter = true,
sqlExportOptions?: SqlExportOptions,
openAfterExport = false,
columnIndexes?: number[],
): Promise<boolean> {
if (rowIds !== undefined || columnIndexes !== undefined || context.value !== "results" || !queryResultExportRequest) {
return false;
}
if (databaseType.value === "mongodb") return false;
@@ -1598,26 +1669,31 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return true;
}
async function exportQueryResultSqlViaBackend(rowIds: number[] | undefined, sqlExportOptions: SqlExportOptions): Promise<boolean> {
if (!isTauriRuntime()) return false;
return exportQueryResultViaBackend("sql", rowIds, false, "name", true, sqlExportOptions);
async function exportQueryResultSqlViaBackend(rowIds: number[] | undefined, sqlExportOptions: SqlExportOptions, columnIndexes?: number[]): Promise<boolean> {
if (!isTauriRuntime() || columnIndexes !== undefined) return false;
return exportQueryResultViaBackend("sql", rowIds, false, "name", true, sqlExportOptions, false, columnIndexes);
}
function sqlExportChoices(fullResult: boolean): SqlExportColumnSelection[] {
const names = context.value === "table-data" ? effectiveColumns(sourceColumns.value, columns.value) : fullResult ? allColumns.value : columns.value;
function sqlExportChoices(fullResult: boolean, columnSubset?: readonly string[]): SqlExportColumnSelection[] {
const names = columnSubset ?? (context.value === "table-data" ? effectiveColumns(sourceColumns.value, columns.value) : fullResult ? allColumns.value : columns.value);
const excluded = new Set((sqlExportExcludedColumns() ?? []).map(normalizeColumnName));
const types = fullResult && context.value === "results" ? allColumnTypes.value : columnTypes.value;
// A subset's sourceIndex is its position within the subset; resolve the
// metadata-less type fallback by name against the typed column list.
const typeNames = fullResult && context.value === "results" ? allColumns.value : context.value === "table-data" ? effectiveColumns(sourceColumns.value, columns.value) : columns.value;
const typeByName = columnSubset ? new Map(typeNames.map((name, index) => [name, types?.[index]])) : undefined;
const metadataByName = new Map((tableMeta.value?.columns ?? []).map((column) => [normalizeColumnName(column.name), column]));
return sqlExportColumnChoices(names).filter((column) => {
if (excluded.has(normalizeColumnName(column.name)) || usesSyntheticRowIdKey(databaseType.value, [column.name])) return false;
const metadata = metadataByName.get(normalizeColumnName(column.name));
if (databaseType.value === "mysql" && /\b(?:virtual|stored|persistent)\s+generated\b|\bgenerated\s+always\s+as\s*\(/i.test(metadata?.extra ?? "")) return false;
const columnType = (metadata?.data_type || types?.[column.sourceIndex])?.trim().replace(/^"|"$/g, "").toLowerCase();
const columnType = (metadata?.data_type || (columnSubset ? typeByName?.get(column.name) : types?.[column.sourceIndex]))?.trim().replace(/^"|"$/g, "").toLowerCase();
return databaseType.value !== "postgres" || (columnType !== "tsvector" && !columnType?.endsWith(".tsvector"));
});
}
async function exportSql(rowIds?: number[]) {
async function exportSql(target?: ExportTargetParam, columnIndexesParam?: number[]) {
const { rowIds, columnIndexes } = normalizeExportTarget(target, columnIndexesParam);
await runExclusiveExport(async () => {
const exportId = uuid();
const exportStartedAt = performance.now();
@@ -1643,7 +1719,11 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
},
true,
);
const selectedSqlExportOptions = (await showSqlInsertModeDialog({ allowSplit: rowIds === undefined && context.value === "table-data", columns: sqlExportChoices(rowIds === undefined) })) as SqlExportOptions | SqlInsertMode | null;
const chosenColumns = columnIndexes !== undefined ? columnIndexes.map((i) => columns.value[i]).filter((c): c is string => !!c) : undefined;
const selectedSqlExportOptions = (await showSqlInsertModeDialog({
allowSplit: rowIds === undefined && columnIndexes === undefined && context.value === "table-data",
columns: sqlExportChoices(rowIds === undefined, chosenColumns),
})) as SqlExportOptions | SqlInsertMode | null;
if (selectedSqlExportOptions === null) {
logExportStage("cancelled", { stage: "insert-mode-dialog" });
return;
@@ -1654,7 +1734,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
try {
// Step 1: table-data context — existing backend table export
logExportStage("backend-export-start");
const handledByBackend = await exportFullTableDataViaBackend("sql", rowIds, "name", true, sqlExportOptions);
const handledByBackend = await exportFullTableDataViaBackend("sql", rowIds, "name", true, sqlExportOptions, false, columnIndexes);
logExportStage("backend-export-finished", { handledByBackend });
if (handledByBackend) {
logExportStage("done", { path: "backend" });
@@ -1663,7 +1743,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
// Step 2: query-result context — NEW backend streaming with background task
logExportStage("query-backend-export-start");
const handledQueryByBackend = await exportQueryResultSqlViaBackend(rowIds, sqlExportOptions);
const handledQueryByBackend = await exportQueryResultSqlViaBackend(rowIds, sqlExportOptions, columnIndexes);
logExportStage("query-backend-export-finished", { handledByBackend: handledQueryByBackend });
if (handledQueryByBackend) {
logExportStage("done", { path: "query-backend" });
@@ -1671,7 +1751,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
}
// Step 3: fallback — local export (Web and edge-case scenarios)
const result = await resultToExport(rowIds, undefined, true, false);
const result = await resultToExport({ rowIds, columnIndexes }, undefined, true, false);
logExportStage(
"result-ready",
{
@@ -1727,14 +1807,15 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
});
}
async function exportCurrentPageSql() {
async function exportCurrentPageSql(columnIndexes?: number[]) {
await runExclusiveExport(async () => {
const selectedSqlExportOptions = (await showSqlInsertModeDialog({ columns: sqlExportChoices(false) })) as SqlExportOptions | SqlInsertMode | null;
const chosenColumns = columnIndexes !== undefined ? columnIndexes.map((i) => columns.value[i]).filter((c): c is string => !!c) : undefined;
const selectedSqlExportOptions = (await showSqlInsertModeDialog({ columns: sqlExportChoices(false, chosenColumns) })) as SqlExportOptions | SqlInsertMode | null;
if (selectedSqlExportOptions === null) return;
const sqlExportOptions: SqlExportOptions = typeof selectedSqlExportOptions === "string" ? { insertMode: selectedSqlExportOptions } : selectedSqlExportOptions;
const insertMode = sqlExportOptions.insertMode;
try {
const result = await resultToExport(undefined, undefined, false, false);
const result = await resultToExport({ columnIndexes }, undefined, false, false);
const exportData = sqlInsertExportData(result, sqlExportOptions.selectedColumns);
const content = await formatSqlInsert({
databaseType: databaseType.value,
+16
View File
@@ -1910,6 +1910,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Seçilmiş sətirləri HTML kimi ixrac et",
exportSelectedRowsSql: "Seçilmiş sətirləri SQL INSERT kimi ixrac et",
exportSelectedRowsTxt: "Seçilmiş sətirləri TXT kimi ixrac et",
exportSelectedColumnsCsv: "Seçilmiş Sütunları CSV kimi ixrac et",
exportSelectedColumnsXlsx: "Seçilmiş Sütunları XLSX kimi ixrac et",
exportSelectedColumnsXlsxWithSql: "Seçilmiş Sütunları SQL ilə XLSX kimi ixrac et",
exportSelectedColumnsJson: "Seçilmiş Sütunları JSON kimi ixrac et",
exportSelectedColumnsMarkdown: "Seçilmiş Sütunları Markdown kimi ixrac et",
exportSelectedColumnsHtml: "Seçilmiş Sütunları HTML kimi ixrac et",
exportSelectedColumnsSql: "Seçilmiş Sütunları SQL INSERT kimi ixrac et",
exportSelectedColumnsTxt: "Seçilmiş Sütunları TXT kimi ixrac et",
exportSelectedCellsCsv: "Seçilmiş Hüceyrələri CSV kimi ixrac et",
exportSelectedCellsXlsx: "Seçilmiş Hüceyrələri XLSX kimi ixrac et",
exportSelectedCellsXlsxWithSql: "Seçilmiş Hüceyrələri SQL ilə XLSX kimi ixrac et",
exportSelectedCellsJson: "Seçilmiş Hüceyrələri JSON kimi ixrac et",
exportSelectedCellsMarkdown: "Seçilmiş Hüceyrələri Markdown kimi ixrac et",
exportSelectedCellsHtml: "Seçilmiş Hüceyrələri HTML kimi ixrac et",
exportSelectedCellsSql: "Seçilmiş Hüceyrələri SQL INSERT kimi ixrac et",
exportSelectedCellsTxt: "Seçilmiş Hüceyrələri TXT kimi ixrac et",
exported: "İxrac edildi",
exportFailed: "İxrac etmək mümkün olmadı: {message}",
xlsxHeaderTitle: "Başlıq formatı",
+16
View File
@@ -2323,6 +2323,22 @@ export default {
exportSelectedRowsHtml: "Export Selected Rows as HTML",
exportSelectedRowsSql: "Export Selected Rows as SQL INSERT",
exportSelectedRowsTxt: "Export Selected Rows as TXT",
exportSelectedColumnsCsv: "Export Selected Columns as CSV",
exportSelectedColumnsXlsx: "Export Selected Columns as XLSX",
exportSelectedColumnsXlsxWithSql: "Export Selected Columns as XLSX with SQL",
exportSelectedColumnsJson: "Export Selected Columns as JSON",
exportSelectedColumnsMarkdown: "Export Selected Columns as Markdown",
exportSelectedColumnsHtml: "Export Selected Columns as HTML",
exportSelectedColumnsSql: "Export Selected Columns as SQL INSERT",
exportSelectedColumnsTxt: "Export Selected Columns as TXT",
exportSelectedCellsCsv: "Export Selected Cells as CSV",
exportSelectedCellsXlsx: "Export Selected Cells as XLSX",
exportSelectedCellsXlsxWithSql: "Export Selected Cells as XLSX with SQL",
exportSelectedCellsJson: "Export Selected Cells as JSON",
exportSelectedCellsMarkdown: "Export Selected Cells as Markdown",
exportSelectedCellsHtml: "Export Selected Cells as HTML",
exportSelectedCellsSql: "Export Selected Cells as SQL INSERT",
exportSelectedCellsTxt: "Export Selected Cells as TXT",
exported: "Exported",
exportFailed: "Export failed: {message}",
xlsxHeaderTitle: "Header Format",
+16
View File
@@ -2184,6 +2184,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Exportar filas seleccionadas como HTML",
exportSelectedRowsSql: "Exportar filas seleccionadas como SQL INSERT",
exportSelectedRowsTxt: "Exportar filas seleccionadas como TXT",
exportSelectedColumnsCsv: "Exportar columnas seleccionadas como CSV",
exportSelectedColumnsXlsx: "Exportar columnas seleccionadas como XLSX",
exportSelectedColumnsXlsxWithSql: "Exportar columnas seleccionadas como XLSX con SQL",
exportSelectedColumnsJson: "Exportar columnas seleccionadas como JSON",
exportSelectedColumnsMarkdown: "Exportar columnas seleccionadas como Markdown",
exportSelectedColumnsHtml: "Exportar columnas seleccionadas como HTML",
exportSelectedColumnsSql: "Exportar columnas seleccionadas como SQL INSERT",
exportSelectedColumnsTxt: "Exportar columnas seleccionadas como TXT",
exportSelectedCellsCsv: "Exportar celdas seleccionadas como CSV",
exportSelectedCellsXlsx: "Exportar celdas seleccionadas como XLSX",
exportSelectedCellsXlsxWithSql: "Exportar celdas seleccionadas como XLSX con SQL",
exportSelectedCellsJson: "Exportar celdas seleccionadas como JSON",
exportSelectedCellsMarkdown: "Exportar celdas seleccionadas como Markdown",
exportSelectedCellsHtml: "Exportar celdas seleccionadas como HTML",
exportSelectedCellsSql: "Exportar celdas seleccionadas como SQL INSERT",
exportSelectedCellsTxt: "Exportar celdas seleccionadas como TXT",
exported: "Exportado",
exportFailed: "Error al exportar: {message}",
copied: "Copiado",
+16
View File
@@ -2182,6 +2182,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Ekspor Baris Terpilih sebagai HTML",
exportSelectedRowsSql: "Ekspor Baris Terpilih sebagai SQL INSERT",
exportSelectedRowsTxt: "Ekspor Baris Terpilih sebagai TXT",
exportSelectedColumnsCsv: "Ekspor Kolom Terpilih sebagai CSV",
exportSelectedColumnsXlsx: "Ekspor Kolom Terpilih sebagai XLSX",
exportSelectedColumnsXlsxWithSql: "Ekspor Kolom Terpilih sebagai XLSX dengan SQL",
exportSelectedColumnsJson: "Ekspor Kolom Terpilih sebagai JSON",
exportSelectedColumnsMarkdown: "Ekspor Kolom Terpilih sebagai Markdown",
exportSelectedColumnsHtml: "Ekspor Kolom Terpilih sebagai HTML",
exportSelectedColumnsSql: "Ekspor Kolom Terpilih sebagai SQL INSERT",
exportSelectedColumnsTxt: "Ekspor Kolom Terpilih sebagai TXT",
exportSelectedCellsCsv: "Ekspor Sel Terpilih sebagai CSV",
exportSelectedCellsXlsx: "Ekspor Sel Terpilih sebagai XLSX",
exportSelectedCellsXlsxWithSql: "Ekspor Sel Terpilih sebagai XLSX dengan SQL",
exportSelectedCellsJson: "Ekspor Sel Terpilih sebagai JSON",
exportSelectedCellsMarkdown: "Ekspor Sel Terpilih sebagai Markdown",
exportSelectedCellsHtml: "Ekspor Sel Terpilih sebagai HTML",
exportSelectedCellsSql: "Ekspor Sel Terpilih sebagai SQL INSERT",
exportSelectedCellsTxt: "Ekspor Sel Terpilih sebagai TXT",
exported: "Diekspor",
exportFailed: "Ekspor gagal: {message}",
xlsxHeaderTitle: "Format Header",
+16
View File
@@ -2082,6 +2082,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Esporta Righe Selezionate come HTML",
exportSelectedRowsSql: "Esporta Righe Selezionate come SQL INSERT",
exportSelectedRowsTxt: "Esporta Righe Selezionate come TXT",
exportSelectedColumnsCsv: "Esporta Colonne Selezionate come CSV",
exportSelectedColumnsXlsx: "Esporta Colonne Selezionate come XLSX",
exportSelectedColumnsXlsxWithSql: "Esporta Colonne Selezionate come XLSX con SQL",
exportSelectedColumnsJson: "Esporta Colonne Selezionate come JSON",
exportSelectedColumnsMarkdown: "Esporta Colonne Selezionate come Markdown",
exportSelectedColumnsHtml: "Esporta Colonne Selezionate come HTML",
exportSelectedColumnsSql: "Esporta Colonne Selezionate come SQL INSERT",
exportSelectedColumnsTxt: "Esporta Colonne Selezionate come TXT",
exportSelectedCellsCsv: "Esporta Celle Selezionate come CSV",
exportSelectedCellsXlsx: "Esporta Celle Selezionate come XLSX",
exportSelectedCellsXlsxWithSql: "Esporta Celle Selezionate come XLSX con SQL",
exportSelectedCellsJson: "Esporta Celle Selezionate come JSON",
exportSelectedCellsMarkdown: "Esporta Celle Selezionate come Markdown",
exportSelectedCellsHtml: "Esporta Celle Selezionate come HTML",
exportSelectedCellsSql: "Esporta Celle Selezionate come SQL INSERT",
exportSelectedCellsTxt: "Esporta Celle Selezionate come TXT",
exported: "Esportato",
exportFailed: "Esportazione non riuscita: {message}",
copied: "Copiato",
+16
View File
@@ -2091,6 +2091,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "選択行をHTMLでエクスポート",
exportSelectedRowsSql: "選択行をSQL INSERTでエクスポート",
exportSelectedRowsTxt: "選択行をTXTでエクスポート",
exportSelectedColumnsCsv: "選択した列を CSV としてエクスポート",
exportSelectedColumnsXlsx: "選択した列を XLSX としてエクスポート",
exportSelectedColumnsXlsxWithSql: "選択した列を SQL 付き XLSX としてエクスポート",
exportSelectedColumnsJson: "選択した列を JSON としてエクスポート",
exportSelectedColumnsMarkdown: "選択した列を Markdown としてエクスポート",
exportSelectedColumnsHtml: "選択した列を HTML としてエクスポート",
exportSelectedColumnsSql: "選択した列を SQL INSERT としてエクスポート",
exportSelectedColumnsTxt: "選択した列を TXT としてエクスポート",
exportSelectedCellsCsv: "選択したセルを CSV としてエクスポート",
exportSelectedCellsXlsx: "選択したセルを XLSX としてエクスポート",
exportSelectedCellsXlsxWithSql: "選択したセルを SQL 付き XLSX としてエクスポート",
exportSelectedCellsJson: "選択したセルを JSON としてエクスポート",
exportSelectedCellsMarkdown: "選択したセルを Markdown としてエクスポート",
exportSelectedCellsHtml: "選択したセルを HTML としてエクスポート",
exportSelectedCellsSql: "選択したセルを SQL INSERT としてエクスポート",
exportSelectedCellsTxt: "選択したセルを TXT としてエクスポート",
exported: "エクスポートしました",
exportFailed: "エクスポートに失敗しました: {message}",
copied: "コピーしました",
+16
View File
@@ -2131,6 +2131,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "선택한 행을 HTML로 내보내기",
exportSelectedRowsSql: "선택한 행을 SQL INSERT로 내보내기",
exportSelectedRowsTxt: "선택한 행을 TXT로 내보내기",
exportSelectedColumnsCsv: "선택한 열을 CSV로 내보내기",
exportSelectedColumnsXlsx: "선택한 열을 XLSX로 내보내기",
exportSelectedColumnsXlsxWithSql: "선택한 열을 SQL 포함 XLSX로 내보내기",
exportSelectedColumnsJson: "선택한 열을 JSON으로 내보내기",
exportSelectedColumnsMarkdown: "선택한 열을 Markdown으로 내보내기",
exportSelectedColumnsHtml: "선택한 열을 HTML로 내보내기",
exportSelectedColumnsSql: "선택한 열을 SQL INSERT로 내보내기",
exportSelectedColumnsTxt: "선택한 열을 TXT로 내보내기",
exportSelectedCellsCsv: "선택한 셀을 CSV로 내보내기",
exportSelectedCellsXlsx: "선택한 셀을 XLSX로 내보내기",
exportSelectedCellsXlsxWithSql: "선택한 셀을 SQL 포함 XLSX로 내보내기",
exportSelectedCellsJson: "선택한 셀을 JSON으로 내보내기",
exportSelectedCellsMarkdown: "선택한 셀을 Markdown으로 내보내기",
exportSelectedCellsHtml: "선택한 셀을 HTML로 내보내기",
exportSelectedCellsSql: "선택한 셀을 SQL INSERT로 내보내기",
exportSelectedCellsTxt: "선택한 셀을 TXT로 내보내기",
exported: "내보냈습니다",
exportFailed: "내보내기 실패: {message}",
copied: "복사했습니다",
+16
View File
@@ -2084,6 +2084,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Exportar Linhas Selecionadas como HTML",
exportSelectedRowsSql: "Exportar Linhas Selecionadas como SQL INSERT",
exportSelectedRowsTxt: "Exportar Linhas Selecionadas como TXT",
exportSelectedColumnsCsv: "Exportar Colunas Selecionadas como CSV",
exportSelectedColumnsXlsx: "Exportar Colunas Selecionadas como XLSX",
exportSelectedColumnsXlsxWithSql: "Exportar Colunas Selecionadas como XLSX com SQL",
exportSelectedColumnsJson: "Exportar Colunas Selecionadas como JSON",
exportSelectedColumnsMarkdown: "Exportar Colunas Selecionadas como Markdown",
exportSelectedColumnsHtml: "Exportar Colunas Selecionadas como HTML",
exportSelectedColumnsSql: "Exportar Colunas Selecionadas como SQL INSERT",
exportSelectedColumnsTxt: "Exportar Colunas Selecionadas como TXT",
exportSelectedCellsCsv: "Exportar Células Selecionadas como CSV",
exportSelectedCellsXlsx: "Exportar Células Selecionadas como XLSX",
exportSelectedCellsXlsxWithSql: "Exportar Células Selecionadas como XLSX com SQL",
exportSelectedCellsJson: "Exportar Células Selecionadas como JSON",
exportSelectedCellsMarkdown: "Exportar Células Selecionadas como Markdown",
exportSelectedCellsHtml: "Exportar Células Selecionadas como HTML",
exportSelectedCellsSql: "Exportar Células Selecionadas como SQL INSERT",
exportSelectedCellsTxt: "Exportar Células Selecionadas como TXT",
exported: "Exportado",
exportFailed: "Falha na exportação: {message}",
copied: "Copiado",
+16
View File
@@ -2221,6 +2221,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Экспорт выбранных строк в HTML",
exportSelectedRowsSql: "Экспорт выбранных строк в SQL INSERT",
exportSelectedRowsTxt: "Экспорт выбранных строк в TXT",
exportSelectedColumnsCsv: "Экспорт выбранных столбцов в CSV",
exportSelectedColumnsXlsx: "Экспорт выбранных столбцов в XLSX",
exportSelectedColumnsXlsxWithSql: "Экспорт выбранных столбцов в XLSX с SQL",
exportSelectedColumnsJson: "Экспорт выбранных столбцов в JSON",
exportSelectedColumnsMarkdown: "Экспорт выбранных столбцов в Markdown",
exportSelectedColumnsHtml: "Экспорт выбранных столбцов в HTML",
exportSelectedColumnsSql: "Экспорт выбранных столбцов в SQL INSERT",
exportSelectedColumnsTxt: "Экспорт выбранных столбцов в TXT",
exportSelectedCellsCsv: "Экспорт выбранных ячеек в CSV",
exportSelectedCellsXlsx: "Экспорт выбранных ячеек в XLSX",
exportSelectedCellsXlsxWithSql: "Экспорт выбранных ячеек в XLSX с SQL",
exportSelectedCellsJson: "Экспорт выбранных ячеек в JSON",
exportSelectedCellsMarkdown: "Экспорт выбранных ячеек в Markdown",
exportSelectedCellsHtml: "Экспорт выбранных ячеек в HTML",
exportSelectedCellsSql: "Экспорт выбранных ячеек в SQL INSERT",
exportSelectedCellsTxt: "Экспорт выбранных ячеек в TXT",
exported: "Экспортировано",
exportFailed: "Не удалось экспортировать: {message}",
xlsxHeaderTitle: "Формат заголовков",
+16
View File
@@ -1903,6 +1903,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "Seçili Satırları HTML Olarak Dışa Aktar",
exportSelectedRowsSql: "Seçili Satırları SQL INSERT Olarak Dışa Aktar",
exportSelectedRowsTxt: "Seçili Satırları TXT Olarak Dışa Aktar",
exportSelectedColumnsCsv: "Seçili Sütunları CSV Olarak Dışa Aktar",
exportSelectedColumnsXlsx: "Seçili Sütunları XLSX Olarak Dışa Aktar",
exportSelectedColumnsXlsxWithSql: "Seçili Sütunları SQL ile XLSX Olarak Dışa Aktar",
exportSelectedColumnsJson: "Seçili Sütunları JSON Olarak Dışa Aktar",
exportSelectedColumnsMarkdown: "Seçili Sütunları Markdown Olarak Dışa Aktar",
exportSelectedColumnsHtml: "Seçili Sütunları HTML Olarak Dışa Aktar",
exportSelectedColumnsSql: "Seçili Sütunları SQL INSERT Olarak Dışa Aktar",
exportSelectedColumnsTxt: "Seçili Sütunları TXT Olarak Dışa Aktar",
exportSelectedCellsCsv: "Seçili Hücreleri CSV Olarak Dışa Aktar",
exportSelectedCellsXlsx: "Seçili Hücreleri XLSX Olarak Dışa Aktar",
exportSelectedCellsXlsxWithSql: "Seçili Hücreleri SQL ile XLSX Olarak Dışa Aktar",
exportSelectedCellsJson: "Seçili Hücreleri JSON Olarak Dışa Aktar",
exportSelectedCellsMarkdown: "Seçili Hücreleri Markdown Olarak Dışa Aktar",
exportSelectedCellsHtml: "Seçili Hücreleri HTML Olarak Dışa Aktar",
exportSelectedCellsSql: "Seçili Hücreleri SQL INSERT Olarak Dışa Aktar",
exportSelectedCellsTxt: "Seçili Hücreleri TXT Olarak Dışa Aktar",
exported: "Dışa aktarıldı",
exportFailed: "Dışa aktarma başarısız: {message}",
xlsxHeaderTitle: "Başlık Biçimi",
+16
View File
@@ -2257,6 +2257,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "导出选中行为 HTML",
exportSelectedRowsSql: "导出选中行为 SQL INSERT",
exportSelectedRowsTxt: "导出选中行为 TXT",
exportSelectedColumnsCsv: "导出选中列为 CSV",
exportSelectedColumnsXlsx: "导出选中列为 XLSX",
exportSelectedColumnsXlsxWithSql: "导出选中列为 XLSX(含 SQL)",
exportSelectedColumnsJson: "导出选中列为 JSON",
exportSelectedColumnsMarkdown: "导出选中列为 Markdown",
exportSelectedColumnsHtml: "导出选中列为 HTML",
exportSelectedColumnsSql: "导出选中列为 SQL INSERT",
exportSelectedColumnsTxt: "导出选中列为 TXT",
exportSelectedCellsCsv: "导出选区为 CSV",
exportSelectedCellsXlsx: "导出选区为 XLSX",
exportSelectedCellsXlsxWithSql: "导出选区为 XLSX(含 SQL)",
exportSelectedCellsJson: "导出选区为 JSON",
exportSelectedCellsMarkdown: "导出选区为 Markdown",
exportSelectedCellsHtml: "导出选区为 HTML",
exportSelectedCellsSql: "导出选区为 SQL INSERT",
exportSelectedCellsTxt: "导出选区为 TXT",
exported: "导出完成",
exportFailed: "导出失败:{message}",
xlsxHeaderTitle: "表头格式",
+16
View File
@@ -2084,6 +2084,22 @@ export default withEnglishFallback({
exportSelectedRowsHtml: "匯出選取資料列為 HTML",
exportSelectedRowsSql: "匯出選取資料列為 SQL INSERT",
exportSelectedRowsTxt: "匯出選取資料列為 TXT",
exportSelectedColumnsCsv: "匯出選取欄為 CSV",
exportSelectedColumnsXlsx: "匯出選取欄為 XLSX",
exportSelectedColumnsXlsxWithSql: "匯出選取欄為 XLSX(含 SQL)",
exportSelectedColumnsJson: "匯出選取欄為 JSON",
exportSelectedColumnsMarkdown: "匯出選取欄為 Markdown",
exportSelectedColumnsHtml: "匯出選取欄為 HTML",
exportSelectedColumnsSql: "匯出選取欄為 SQL INSERT",
exportSelectedColumnsTxt: "匯出選取欄為 TXT",
exportSelectedCellsCsv: "匯出選取範圍為 CSV",
exportSelectedCellsXlsx: "匯出選取範圍為 XLSX",
exportSelectedCellsXlsxWithSql: "匯出選取範圍為 XLSX(含 SQL)",
exportSelectedCellsJson: "匯出選取範圍為 JSON",
exportSelectedCellsMarkdown: "匯出選取範圍為 Markdown",
exportSelectedCellsHtml: "匯出選取範圍為 HTML",
exportSelectedCellsSql: "匯出選取範圍為 SQL INSERT",
exportSelectedCellsTxt: "匯出選取範圍為 TXT",
exported: "匯出完成",
exportFailed: "匯出失敗:{message}",
copied: "已複製",