From 741783445d59a4a4840622fd9f57666a44d53d3c Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Sun, 10 May 2026 23:38:53 +0800
Subject: [PATCH] feat: add XLSX export to table context menu
Add XLSX export option to the sidebar table name right-click menu,
unifying export capabilities with the data grid context menu. Also
add toast notifications for all sidebar data export operations.
---
src/components/sidebar/TreeItem.vue | 58 ++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/src/components/sidebar/TreeItem.vue b/src/components/sidebar/TreeItem.vue
index 41b8b8938f..6b1bf5b1fc 100644
--- a/src/components/sidebar/TreeItem.vue
+++ b/src/components/sidebar/TreeItem.vue
@@ -830,6 +830,33 @@ async function saveFileContent(content: string, defaultFileName: string, filterN
}
}
+async function saveBinaryFileContent(
+ content: Uint8Array,
+ defaultFileName: string,
+ filterName: string,
+ filterExt: string,
+) {
+ 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) await writeFile(path, content);
+ } 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);
+ }
+}
+
async function exportDatabase() {
const node = props.node;
if (!(node.type === "database" || node.type === "schema") || !node.connectionId || !node.database) return;
@@ -942,8 +969,36 @@ async function exportData(format: "csv" | "json" | "sql") {
}
await saveFileContent(content, `${node.label}.${ext}`, ext.toUpperCase(), ext);
+ toast(t("grid.exported"));
} catch (e: any) {
- console.error("Export data failed:", e);
+ toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
+ }
+}
+
+async function exportDataXlsx() {
+ const node = props.node;
+ if (!node.connectionId || !node.database) return;
+ const config = connectionStore.getConfig(node.connectionId);
+ if (!config) return;
+
+ try {
+ await connectionStore.ensureConnected(node.connectionId);
+ const qualifiedName =
+ isSchemaAware(config.db_type) && node.schema
+ ? `${quoteIdent(node.schema)}.${quoteIdent(node.label)}`
+ : quoteIdent(node.label);
+ const result = await api.executeQuery(node.connectionId, node.database, `SELECT * FROM ${qualifiedName}`);
+
+ 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");
+ toast(t("grid.exported"));
+ } catch (e: any) {
+ toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
}
}
@@ -1645,6 +1700,7 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
CSV
JSON
SQL INSERT
+ XLSX