feat(sql): add save/open SQL file buttons and enable tab persistence on desktop
This commit is contained in:
parent
2de0894a99
commit
c295bf5780
61
src/App.vue
61
src/App.vue
|
|
@ -132,6 +132,66 @@ function formatActiveSql() {
|
|||
formatSqlRequestId.value++;
|
||||
}
|
||||
|
||||
async function saveSqlToFile() {
|
||||
const tab = activeTab.value;
|
||||
if (!tab || !tab.sql.trim()) return;
|
||||
try {
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const { writeTextFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await save({ defaultPath: `${tab.title}.sql`, filters: [{ name: "SQL", extensions: ["sql"] }] });
|
||||
if (path) {
|
||||
await writeTextFile(path, tab.sql);
|
||||
toast(t("toolbar.sqlSaved"), 2000);
|
||||
}
|
||||
} else {
|
||||
const blob = new Blob([tab.sql], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${tab.title}.sql`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(t("toolbar.sqlSaveFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function openSqlFile() {
|
||||
const tab = activeTab.value;
|
||||
if (!tab) return;
|
||||
try {
|
||||
if (isTauriRuntime()) {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const { readTextFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await open({ filters: [{ name: "SQL", extensions: ["sql"] }], multiple: false });
|
||||
if (path) {
|
||||
const content = await readTextFile(path as string);
|
||||
queryStore.updateSql(tab.id, content);
|
||||
}
|
||||
} else {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".sql";
|
||||
input.onchange = () => {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (typeof reader.result === "string") {
|
||||
queryStore.updateSql(tab.id, reader.result);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(t("toolbar.sqlOpenFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function newQuery() {
|
||||
const connId = connectionStore.activeConnectionId || connectionStore.connections[0]?.id;
|
||||
if (!connId) return;
|
||||
|
|
@ -295,6 +355,7 @@ onUnmounted(() => { window.removeEventListener("keydown", handleKeydown, true);
|
|||
:active-tab="activeTab" :active-connection="activeConnection" :executable-sql="executableSql"
|
||||
@execute="tryExecute()" @cancel="cancelActiveExecution()" @explain="tryExplain()"
|
||||
@format-sql="formatActiveSql"
|
||||
@save-sql="saveSqlToFile" @open-sql="openSqlFile"
|
||||
@change-connection="changeActiveConnection" @change-database="changeActiveDatabase"
|
||||
/>
|
||||
<ContentArea
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Play, Loader2, Square, Database, Table2, AlignLeft, GitBranch } from "lucide-vue-next";
|
||||
import { Play, Loader2, Square, Database, Table2, AlignLeft, GitBranch, Save, FolderOpen } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -29,6 +29,8 @@ const emit = defineEmits<{
|
|||
cancel: [];
|
||||
explain: [];
|
||||
formatSql: [];
|
||||
saveSql: [];
|
||||
openSql: [];
|
||||
changeConnection: [connectionId: string];
|
||||
changeDatabase: [database: string];
|
||||
}>();
|
||||
|
|
@ -94,6 +96,22 @@ function databaseDisplayName(database: string): string {
|
|||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t('toolbar.formatSql') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" :disabled="!activeTab.sql.trim()" @click="emit('saveSql')">
|
||||
<Save class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t('toolbar.saveSql') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('openSql')">
|
||||
<FolderOpen class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t('toolbar.openSql') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span class="flex-1 min-w-0" />
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ export default {
|
|||
stopExplain: "Stop explain",
|
||||
formatSql: "Format SQL",
|
||||
formatSqlFailed: "Failed to format SQL",
|
||||
saveSql: "Save SQL to file",
|
||||
openSql: "Open SQL file",
|
||||
sqlSaved: "SQL saved",
|
||||
sqlOpenFailed: "Failed to open file: {message}",
|
||||
sqlSaveFailed: "Failed to save file: {message}",
|
||||
},
|
||||
updates: {
|
||||
title: "Updates",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ export default {
|
|||
stopExplain: "停止执行计划",
|
||||
formatSql: "格式化 SQL",
|
||||
formatSqlFailed: "SQL 格式化失败",
|
||||
saveSql: "保存 SQL 到文件",
|
||||
openSql: "打开 SQL 文件",
|
||||
sqlSaved: "SQL 已保存",
|
||||
sqlOpenFailed: "打开文件失败:{message}",
|
||||
sqlSaveFailed: "保存文件失败:{message}",
|
||||
},
|
||||
updates: {
|
||||
title: "更新",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import { closeAllTabsState, closeOtherTabsState } from "@/lib/tabCloseActions";
|
|||
import { buildExplainSql, parseExplainResult } from "@/lib/explainPlan";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
|
||||
interface SavedTab {
|
||||
id: string;
|
||||
title: string;
|
||||
|
|
@ -22,10 +20,8 @@ interface SavedTab {
|
|||
|
||||
const STORAGE_KEY = "dbx-open-tabs";
|
||||
const ACTIVE_TAB_KEY = "dbx-active-tab";
|
||||
const isDesktop = isTauriRuntime();
|
||||
|
||||
function saveTabs(tabs: QueryTab[], activeTabId: string | null) {
|
||||
if (isDesktop) return;
|
||||
try {
|
||||
const saved: SavedTab[] = tabs.map(t => ({
|
||||
id: t.id, title: t.title, connectionId: t.connectionId,
|
||||
|
|
@ -38,7 +34,6 @@ function saveTabs(tabs: QueryTab[], activeTabId: string | null) {
|
|||
}
|
||||
|
||||
function loadSavedTabs(): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
if (isDesktop) return { tabs: [], activeTabId: null };
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return { tabs: [], activeTabId: null };
|
||||
|
|
|
|||
Loading…
Reference in New Issue