feat(welcome-screen): 连接描述截断时显示工具提示

This commit is contained in:
二丫讲梵 2026-07-09 11:19:03 +08:00 committed by GitHub
parent a8a7d3e9ed
commit b31d52c00c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 32 deletions

View File

@ -2,6 +2,7 @@
import { useI18n } from "vue-i18n";
import { FilePlus2, Plus, History, Download, Database, Search, ShieldCheck, Sparkles } from "@lucide/vue";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import TruncatedTextTooltip from "@/components/ui/TruncatedTextTooltip.vue";
import { connectionDriverLabel, connectionIconType, connectionRedactedNameLabel, connectionRedactedOptionSubtitle } from "@/lib/connection/connectionPresentation";
import type { ConnectionConfig } from "@/types/database";
@ -34,42 +35,50 @@ const emit = defineEmits<{
}>();
const { t } = useI18n();
function welcomeConnectionSubtitle(connection: ConnectionConfig): string {
return connectionRedactedOptionSubtitle(connection) || connectionDriverLabel(connection);
}
</script>
<template>
<div class="min-w-0 flex-1 overflow-x-hidden overflow-y-auto bg-background">
<div class="mx-auto flex min-h-full w-full min-w-0 max-w-5xl flex-col justify-center gap-6 px-8 py-10">
<div class="grid grid-cols-1 gap-3 sm:grid-cols-3">
<div class="rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex items-center gap-2 text-xs text-muted-foreground"><Database class="h-3.5 w-3.5" /> {{ t("welcome.connections") }}</div>
<div class="grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-3">
<div class="min-w-0 overflow-hidden rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
<Database class="h-3.5 w-3.5 shrink-0" /> <span class="min-w-0 truncate">{{ t("welcome.connections") }}</span>
</div>
<div class="mt-2 text-2xl font-semibold">{{ connectionStats.total }}</div>
</div>
<div class="rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex items-center gap-2 text-xs text-muted-foreground"><ShieldCheck class="h-3.5 w-3.5" /> {{ t("welcome.connected") }}</div>
<div class="min-w-0 overflow-hidden rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
<ShieldCheck class="h-3.5 w-3.5 shrink-0" /> <span class="min-w-0 truncate">{{ t("welcome.connected") }}</span>
</div>
<div class="mt-2 text-2xl font-semibold">{{ connectionStats.connected }}</div>
</div>
<div class="rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex items-center gap-2 text-xs text-muted-foreground"><Sparkles class="h-3.5 w-3.5" /> {{ t("welcome.databaseTypes") }}</div>
<div class="min-w-0 overflow-hidden rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
<Sparkles class="h-3.5 w-3.5 shrink-0" /> <span class="min-w-0 truncate">{{ t("welcome.databaseTypes") }}</span>
</div>
<div class="mt-2 text-2xl font-semibold">{{ connectionStats.types }}</div>
</div>
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-[1.2fr_0.8fr]">
<div class="rounded-lg border">
<div class="grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
<div class="min-w-0 overflow-hidden rounded-lg border">
<div class="flex items-center justify-between border-b px-4 py-3">
<div class="text-sm font-medium">{{ t("welcome.quickConnections") }}</div>
</div>
<div class="divide-y">
<button v-for="connection in recentConnections" :key="connection.id" class="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-muted/40" @click="emit('open-connection-query', connection.id)">
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-4 w-4" />
<button v-for="connection in recentConnections" :key="connection.id" class="flex w-full min-w-0 items-center gap-3 overflow-hidden px-4 py-3 text-left hover:bg-muted/40" @click="emit('open-connection-query', connection.id)">
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-4 w-4 shrink-0" />
<span class="h-5 w-1 rounded-full shrink-0" :style="{ backgroundColor: connection.color || '#9ca3af' }" />
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium">{{ connectionRedactedNameLabel(connection) }}</div>
<div class="truncate text-xs text-muted-foreground">
{{ connectionRedactedOptionSubtitle(connection) || connectionDriverLabel(connection) }}
</div>
<TruncatedTextTooltip :text="connectionRedactedNameLabel(connection)" class="block text-sm font-medium" />
<TruncatedTextTooltip :text="welcomeConnectionSubtitle(connection)" class="block text-xs text-muted-foreground" tooltip-class="max-w-[min(42rem,calc(100vw-2rem))] whitespace-pre-wrap break-all px-3 py-2 text-left leading-5" />
</div>
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
<FilePlus2 class="h-4 w-4 shrink-0 text-muted-foreground" />
</button>
<div v-if="recentConnections.length === 0" class="px-4 py-8 text-sm text-muted-foreground">
{{ t("sidebar.noConnections") }}
@ -77,30 +86,40 @@ const { t } = useI18n();
</div>
</div>
<div class="rounded-lg border">
<div class="min-w-0 overflow-hidden rounded-lg border">
<div class="border-b px-4 py-3">
<div class="text-sm font-medium">{{ t("welcome.shortcuts") }}</div>
</div>
<div class="grid gap-1 p-2">
<button class="flex items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('new-connection')"><Plus class="h-4 w-4" /> {{ t("toolbar.newConnection") }}</button>
<button class="flex items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" :disabled="!hasConnections" @click="emit('new-query')"><FilePlus2 class="h-4 w-4" /> {{ t("toolbar.newQuery") }}</button>
<button class="flex items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('show-history')"><History class="h-4 w-4" /> {{ t("history.title") }}</button>
<button class="flex items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('import-config')"><Download class="h-4 w-4" /> {{ t("sidebar.import") }}</button>
<div class="mt-2 rounded-md bg-muted/30 px-3 py-2 text-xs leading-5 text-muted-foreground">
<Search class="mr-1 inline h-3.5 w-3.5" />
<div class="grid min-w-0 gap-1 p-2">
<button class="flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('new-connection')">
<Plus class="h-4 w-4 shrink-0" /> <span class="min-w-0 truncate">{{ t("toolbar.newConnection") }}</span>
</button>
<button class="flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" :disabled="!hasConnections" @click="emit('new-query')">
<FilePlus2 class="h-4 w-4 shrink-0" /> <span class="min-w-0 truncate">{{ t("toolbar.newQuery") }}</span>
</button>
<button class="flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('show-history')">
<History class="h-4 w-4 shrink-0" /> <span class="min-w-0 truncate">{{ t("history.title") }}</span>
</button>
<button class="flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-3 py-2 text-left text-sm hover:bg-muted/50" @click="emit('import-config')">
<Download class="h-4 w-4 shrink-0" /> <span class="min-w-0 truncate">{{ t("sidebar.import") }}</span>
</button>
<div class="mt-2 min-w-0 overflow-hidden rounded-md bg-muted/30 px-3 py-2 text-xs leading-5 text-muted-foreground">
<Search class="mr-1 inline h-3.5 w-3.5 shrink-0" />
{{ t("welcome.tip") }}
</div>
</div>
</div>
</div>
<div class="rounded-lg border">
<div class="min-w-0 overflow-hidden rounded-lg border">
<div class="flex items-center justify-between border-b px-4 py-3">
<div class="flex items-center gap-2 text-sm font-medium"><History class="h-4 w-4" /> {{ t("welcome.sqlHistory") }}</div>
<div class="flex min-w-0 items-center gap-2 text-sm font-medium">
<History class="h-4 w-4 shrink-0" /> <span class="min-w-0 truncate">{{ t("welcome.sqlHistory") }}</span>
</div>
</div>
<div class="divide-y">
<button v-for="item in savedSqlHistoryItems" :key="item.id" class="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-muted/40" @click="emit('open-saved-sql', item.id)">
<History class="h-4 w-4 text-muted-foreground" />
<button v-for="item in savedSqlHistoryItems" :key="item.id" class="flex w-full min-w-0 items-center gap-3 overflow-hidden px-4 py-3 text-left hover:bg-muted/40" @click="emit('open-saved-sql', item.id)">
<History class="h-4 w-4 shrink-0 text-muted-foreground" />
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium">{{ item.name }}</div>
<div class="truncate text-xs text-muted-foreground">
@ -110,7 +129,7 @@ const { t } = useI18n();
<span v-if="item.openCount"> · {{ t("welcome.sqlHistoryOpenCount", { count: item.openCount }) }}</span>
</div>
</div>
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
<FilePlus2 class="h-4 w-4 shrink-0 text-muted-foreground" />
</button>
<div v-if="savedSqlHistoryItems.length === 0" class="px-4 py-8 text-sm text-muted-foreground">
{{ t("welcome.sqlHistoryEmpty") }}
@ -119,10 +138,10 @@ const { t } = useI18n();
</div>
<!-- MCP Integration Hint -->
<div class="rounded-lg border bg-muted/10 px-5 py-4">
<div class="flex items-start gap-3">
<div class="min-w-0 overflow-hidden rounded-lg border bg-muted/10 px-5 py-4">
<div class="flex min-w-0 items-start gap-3">
<Sparkles class="h-4 w-4 mt-0.5 text-muted-foreground shrink-0" />
<div class="min-w-0">
<div class="min-w-0 flex-1">
<div class="text-sm font-medium">{{ t("welcome.mcpTitle") }}</div>
<p class="mt-1 text-xs leading-5 text-muted-foreground">
{{ t("welcome.mcpDescription") }}

View File

@ -8,6 +8,7 @@ const props = withDefaults(
defineProps<{
text: string;
class?: HTMLAttributes["class"];
tooltipClass?: HTMLAttributes["class"];
side?: "top" | "right" | "bottom" | "left";
sideOffset?: number;
delay?: number;
@ -35,5 +36,8 @@ function isTooltipDisabled(): boolean {
<span ref="textRef" :class="cn('truncate', props.class)">
<slot>{{ text }}</slot>
</span>
<template v-if="tooltipClass" #content>
<span :class="cn('block max-w-xs px-3 py-1.5', tooltipClass)">{{ text }}</span>
</template>
</LightTooltip>
</template>