mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-02 00:29:30 +00:00
feat(export): move csv and xlsx generation to Rust backend
This commit is contained in:
@@ -92,7 +92,7 @@ import {
|
||||
treeNodeRowAction,
|
||||
treeNodeRowDoubleClickAction,
|
||||
} from "@/lib/treeNodeClick";
|
||||
import { formatCsv, formatJson, formatSqlInsert } from "@/lib/exportFormats";
|
||||
import { formatJson, formatSqlInsert } from "@/lib/exportFormats";
|
||||
import { fetchTableDataForExport } from "@/lib/tableDataExport";
|
||||
import {
|
||||
buildCreateDatabaseSql,
|
||||
@@ -1266,13 +1266,26 @@ async function exportData(format: "csv" | "json" | "sql") {
|
||||
executePage: (sql) => api.executeQuery(connectionId, database, sql),
|
||||
});
|
||||
|
||||
if (format === "csv") {
|
||||
let outputPath = `${node.label}.csv`;
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
defaultPath: outputPath,
|
||||
filters: [{ name: "CSV", extensions: ["csv"] }],
|
||||
});
|
||||
if (!path) return;
|
||||
outputPath = path as string;
|
||||
}
|
||||
await api.exportQueryResultCsv(outputPath, result.columns, result.rows);
|
||||
toast(t("grid.exported"));
|
||||
return;
|
||||
}
|
||||
|
||||
let content: string;
|
||||
let ext: string;
|
||||
|
||||
if (format === "csv") {
|
||||
ext = "csv";
|
||||
content = formatCsv(result.columns, result.rows);
|
||||
} else if (format === "json") {
|
||||
if (format === "json") {
|
||||
ext = "json";
|
||||
content = formatJson(result.columns, result.rows);
|
||||
} else {
|
||||
@@ -1311,13 +1324,17 @@ async function exportDataXlsx() {
|
||||
executePage: (sql) => api.executeQuery(connectionId, database, sql),
|
||||
});
|
||||
|
||||
const { buildXlsxWorkbook } = await import("@/lib/xlsxExport");
|
||||
const workbook = buildXlsxWorkbook({
|
||||
sheetName: node.label,
|
||||
columns: result.columns,
|
||||
rows: result.rows,
|
||||
});
|
||||
await saveBinaryFileContent(workbook, `${node.label}.xlsx`, "Excel", "xlsx");
|
||||
let outputPath = `${node.label}.xlsx`;
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
defaultPath: outputPath,
|
||||
filters: [{ name: "Excel", extensions: ["xlsx"] }],
|
||||
});
|
||||
if (!path) return;
|
||||
outputPath = path as string;
|
||||
}
|
||||
await api.exportQueryResultXlsx(outputPath, node.label, result.columns, result.rows);
|
||||
toast(t("grid.exported"));
|
||||
} catch (e: any) {
|
||||
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { computed, type ComputedRef, type Ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { formatCsv, formatJson } from "@/lib/exportFormats";
|
||||
import { formatJson } from "@/lib/exportFormats";
|
||||
import * as api from "@/lib/api";
|
||||
import {
|
||||
formatSelectionAsCsv,
|
||||
formatSelectionAsJson,
|
||||
@@ -266,36 +267,6 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBinaryFileContent(
|
||||
content: Uint8Array,
|
||||
defaultFileName: string,
|
||||
filterName: string,
|
||||
filterExt: string,
|
||||
): Promise<boolean> {
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const { writeFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await save({
|
||||
defaultPath: defaultFileName,
|
||||
filters: [{ name: filterName, extensions: [filterExt] }],
|
||||
});
|
||||
if (!path) return false;
|
||||
await writeFile(path, content);
|
||||
return true;
|
||||
} else {
|
||||
const blob = new Blob([content], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = defaultFileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Export functions ---
|
||||
async function runExclusiveExport(action: () => Promise<void>) {
|
||||
const finish = tryStartExclusiveActivation(exportGuard);
|
||||
@@ -311,9 +282,18 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
await runExclusiveExport(async () => {
|
||||
try {
|
||||
const rows = rowsToExport(rowIds).map((item) => item.data.map((c) => displayCellValue(c)));
|
||||
if (await saveFileContent(formatCsv(columns.value, rows), "export.csv", "CSV", "csv")) {
|
||||
toast(t("grid.exported"));
|
||||
let outputPath = "export.csv";
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
defaultPath: outputPath,
|
||||
filters: [{ name: "CSV", extensions: ["csv"] }],
|
||||
});
|
||||
if (!path) return;
|
||||
outputPath = path as string;
|
||||
}
|
||||
await api.exportQueryResultCsv(outputPath, columns.value, rows);
|
||||
toast(t("grid.exported"));
|
||||
} catch (e: any) {
|
||||
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
@@ -352,15 +332,23 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
async function exportXlsx(rowIds?: number[]) {
|
||||
await runExclusiveExport(async () => {
|
||||
try {
|
||||
const { buildXlsxWorkbook } = await import("@/lib/xlsxExport");
|
||||
const workbook = buildXlsxWorkbook({
|
||||
sheetName: tableMeta.value?.tableName || "Export",
|
||||
columns: columns.value,
|
||||
rows: rowsToExport(rowIds).map((item) => item.data),
|
||||
});
|
||||
if (await saveBinaryFileContent(workbook, "export.xlsx", "Excel", "xlsx")) {
|
||||
toast(t("grid.exported"));
|
||||
let outputPath = "export.xlsx";
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
defaultPath: outputPath,
|
||||
filters: [{ name: "Excel", extensions: ["xlsx"] }],
|
||||
});
|
||||
if (!path) return;
|
||||
outputPath = path as string;
|
||||
}
|
||||
await api.exportQueryResultXlsx(
|
||||
outputPath,
|
||||
tableMeta.value?.tableName || "Export",
|
||||
columns.value,
|
||||
rowsToExport(rowIds).map((item) => item.data),
|
||||
);
|
||||
toast(t("grid.exported"));
|
||||
} catch (e: any) {
|
||||
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
|
||||
@@ -128,6 +128,8 @@ export const cancelTableImport = forward("cancelTableImport");
|
||||
// Database Export
|
||||
export const exportDatabaseSql = forward("exportDatabaseSql");
|
||||
export const cancelDatabaseExport = forward("cancelDatabaseExport");
|
||||
export const exportQueryResultCsv = forward("exportQueryResultCsv");
|
||||
export const exportQueryResultXlsx = forward("exportQueryResultXlsx");
|
||||
|
||||
// Redis
|
||||
export const redisListDatabases = forward("redisListDatabases");
|
||||
|
||||
@@ -47,6 +47,7 @@ import type {
|
||||
TableImportProgress,
|
||||
DatabaseExportRequest,
|
||||
ExportProgress,
|
||||
XlsxCellValue,
|
||||
} from "./tauri";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -680,6 +681,47 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await post("/api/export/database/cancel", { exportId });
|
||||
}
|
||||
|
||||
export async function exportQueryResultCsv(
|
||||
filePath: string,
|
||||
columns: string[],
|
||||
rows: readonly (readonly XlsxCellValue[])[],
|
||||
): Promise<void> {
|
||||
const { formatCsv } = await import("./exportFormats");
|
||||
const content = formatCsv(columns, rows as (string | number | boolean | null)[][]);
|
||||
const fileName = filePath.split(/[\\/]/).pop() || "export.csv";
|
||||
const blob = new Blob(["\uFEFF", content], { type: "text/csv;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export async function exportQueryResultXlsx(
|
||||
filePath: string,
|
||||
sheetName: string | undefined,
|
||||
columns: string[],
|
||||
rows: readonly (readonly XlsxCellValue[])[],
|
||||
): Promise<void> {
|
||||
const { buildXlsxWorkbook } = await import("./xlsxExport");
|
||||
const workbook = buildXlsxWorkbook({
|
||||
sheetName: sheetName || "Export",
|
||||
columns,
|
||||
rows,
|
||||
});
|
||||
const fileName = filePath.split(/[\\/]/).pop() || "export.xlsx";
|
||||
const blob = new Blob([workbook], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Redis
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface DesktopSettings {
|
||||
show_tray_icon: boolean;
|
||||
}
|
||||
|
||||
export type XlsxCellValue = string | number | boolean | null;
|
||||
|
||||
export interface DriverInstallProgress {
|
||||
step: string;
|
||||
downloaded?: number;
|
||||
@@ -974,3 +976,33 @@ export async function exportDatabaseSql(
|
||||
export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await invoke("cancel_database_export", { exportId });
|
||||
}
|
||||
|
||||
export async function exportQueryResultCsv(
|
||||
filePath: string,
|
||||
columns: string[],
|
||||
rows: readonly (readonly XlsxCellValue[])[],
|
||||
): Promise<void> {
|
||||
return invoke("export_query_result_csv", {
|
||||
request: {
|
||||
filePath,
|
||||
columns,
|
||||
rows,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportQueryResultXlsx(
|
||||
filePath: string,
|
||||
sheetName: string | undefined,
|
||||
columns: string[],
|
||||
rows: readonly (readonly XlsxCellValue[])[],
|
||||
): Promise<void> {
|
||||
return invoke("export_query_result_xlsx", {
|
||||
request: {
|
||||
filePath,
|
||||
sheetName,
|
||||
columns,
|
||||
rows,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user