fix(web): align file picker behavior

This commit is contained in:
t8y2 2026-05-09 23:49:18 +08:00
parent 97b77ec46d
commit dc2164dd62
3 changed files with 85 additions and 19 deletions

View File

@ -26,6 +26,7 @@ type ConfigTab = "connection" | "ssh";
const { t } = useI18n();
const { toast } = useToast();
const open = defineModel<boolean>("open", { default: false });
const isDesktop = isTauriRuntime();
const props = defineProps<{
editConfig?: ConnectionConfig;
@ -770,7 +771,7 @@ async function browseDbFilePath() {
<Label class="text-right">{{ t("connection.filePath") }}</Label>
<div class="col-span-3 flex items-center gap-1">
<Input v-model="form.host" class="flex-1" placeholder="/path/to/database.db" />
<Tooltip>
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="browseDbFilePath">
<FolderOpen class="h-4 w-4" />
@ -980,7 +981,7 @@ async function browseDbFilePath() {
placeholder="~/.ssh/id_rsa"
:disabled="!form.ssh_enabled"
/>
<Tooltip>
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button
variant="outline"

View File

@ -40,6 +40,7 @@ const cancelling = ref(false);
const importId = ref("");
const progress = ref<api.TableImportProgress | null>(null);
const errorMessage = ref("");
const fileInput = ref<HTMLInputElement | null>(null);
const selectedConnection = computed(() =>
props.prefillConnectionId ? store.getConfig(props.prefillConnectionId) : undefined,
@ -118,8 +119,34 @@ async function loadTargetColumns() {
}
}
async function previewSelectedImportFile(fileOrPath: string | File) {
if (isTauriRuntime()) {
return api.previewTableImportFile(fileOrPath as string);
}
const { previewTableImportFile } = await import("@/lib/http");
return previewTableImportFile(fileOrPath as File);
}
async function loadPreview(fileOrPath: string | File) {
loadingPreview.value = true;
errorMessage.value = "";
try {
preview.value = await previewSelectedImportFile(fileOrPath);
applyAutoMapping();
} catch (e: any) {
preview.value = null;
columnMapping.value = {};
errorMessage.value = String(e?.message || e);
} finally {
loadingPreview.value = false;
}
}
async function selectFile() {
if (!isTauriRuntime()) return;
if (!isTauriRuntime()) {
fileInput.value?.click();
return;
}
const { open } = await import("@tauri-apps/plugin-dialog");
const selected = await open({
multiple: false,
@ -132,18 +159,15 @@ async function selectFile() {
});
if (!selected || Array.isArray(selected)) return;
loadingPreview.value = true;
errorMessage.value = "";
try {
preview.value = await api.previewTableImportFile(selected);
applyAutoMapping();
} catch (e: any) {
preview.value = null;
columnMapping.value = {};
errorMessage.value = String(e?.message || e);
} finally {
loadingPreview.value = false;
}
await loadPreview(selected);
}
async function handleFileInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
input.value = "";
if (!file || running.value) return;
await loadPreview(file);
}
function updateMapping(sourceColumn: string, value: any) {
@ -227,6 +251,13 @@ watch(open, (value) => {
<div class="space-y-4 py-2">
<div class="grid grid-cols-[1fr_auto] gap-2">
<input
ref="fileInput"
type="file"
accept=".csv,.tsv,.json,.xlsx,.xlsm,.xls"
class="hidden"
@change="handleFileInputChange"
/>
<div class="min-w-0 rounded-md border bg-muted/20 px-3 py-2">
<div class="truncate text-xs text-muted-foreground">{{ t("tableImport.target") }}</div>
<div class="truncate text-sm font-medium">

View File

@ -34,6 +34,7 @@ const props = defineProps<{
const store = useConnectionStore();
const fileInput = ref<HTMLInputElement | null>(null);
const filePath = ref("");
const preview = ref<SqlFilePreview | null>(null);
const selectingFile = ref(false);
@ -198,11 +199,19 @@ async function loadDatabasesForConnection(id: string) {
}
}
async function loadPreview(path: string) {
async function previewSelectedSqlFile(fileOrPath: string | File) {
if (isTauriRuntime()) {
return previewSqlFile(fileOrPath as string);
}
const { previewSqlFile: previewWebSqlFile } = await import("@/lib/http");
return previewWebSqlFile(fileOrPath as File);
}
async function loadPreview(fileOrPath: string | File) {
loadingPreview.value = true;
preview.value = null;
try {
preview.value = await previewSqlFile(path);
preview.value = await previewSelectedSqlFile(fileOrPath);
filePath.value = preview.value.filePath;
resetExecution();
} catch (e: any) {
@ -214,7 +223,10 @@ async function loadPreview(path: string) {
async function selectFile() {
if (running.value) return;
if (!isTauriRuntime()) return;
if (!isTauriRuntime()) {
fileInput.value?.click();
return;
}
selectingFile.value = true;
try {
const { open } = await import("@tauri-apps/plugin-dialog");
@ -232,6 +244,27 @@ async function selectFile() {
}
}
async function handleFileInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
input.value = "";
if (!file || running.value) return;
selectingFile.value = true;
try {
await loadPreview(file);
} finally {
selectingFile.value = false;
}
}
async function listenProgress(id: string, handler: (next: SqlFileProgress) => void): Promise<() => void> {
if (isTauriRuntime()) {
return listenSqlFileProgress(handler);
}
const { listenSqlFileProgressById } = await import("@/lib/http");
return listenSqlFileProgressById(id, handler);
}
async function startExecution() {
if (!canStart.value || !preview.value) return;
@ -253,7 +286,7 @@ async function startExecution() {
return;
}
unlisten = await listenSqlFileProgress((next) => {
unlisten = await listenProgress(id, (next) => {
if (next.executionId !== id) return;
progress.value = next;
terminalStatus.value = next.status;
@ -351,6 +384,7 @@ watch(open, (value) => {
</div>
<div class="flex items-center gap-2">
<input ref="fileInput" type="file" accept=".sql,text/sql" class="hidden" @change="handleFileInputChange" />
<Input
:model-value="filePath"
readonly