diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2125400..0d8db66 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,8 +1,8 @@ import React, { useState, useEffect } from 'react'; -import { Layout, Button, ConfigProvider, theme, Dropdown, MenuProps, message, Modal, Spin, Slider, Progress } from 'antd'; +import { Layout, Button, ConfigProvider, theme, Dropdown, MenuProps, message, Modal, Spin, Slider, Progress, Switch } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import { PlusOutlined, BulbOutlined, BulbFilled, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined } from '@ant-design/icons'; -import { Environment, EventsOn } from '../wailsjs/runtime/runtime'; +import { Environment, EventsOn, WindowFullscreen, WindowIsFullscreen, WindowIsMaximised, WindowMaximise } from '../wailsjs/runtime/runtime'; import Sidebar from './components/Sidebar'; import TabManager from './components/TabManager'; import ConnectionModal from './components/ConnectionModal'; @@ -26,6 +26,8 @@ function App() { const setTheme = useStore(state => state.setTheme); const appearance = useStore(state => state.appearance); const setAppearance = useStore(state => state.setAppearance); + const startupFullscreen = useStore(state => state.startupFullscreen); + const setStartupFullscreen = useStore(state => state.setStartupFullscreen); const darkMode = themeMode === 'dark'; const effectiveOpacity = normalizeOpacityForPlatform(appearance.opacity); const effectiveBlur = normalizeBlurForPlatform(appearance.blur); @@ -56,6 +58,84 @@ function App() { }; }, []); + useEffect(() => { + let cancelled = false; + let startupWindowTimer: number | null = null; + const maxApplyAttempts = 6; + const applyRetryDelayMs = 400; + const settleDelayMs = 160; + + const checkStartupPreferenceApplied = async (): Promise => { + try { + if (await WindowIsFullscreen()) { + return true; + } + } catch (_) { + // ignore + } + try { + if (await WindowIsMaximised()) { + return true; + } + } catch (_) { + // ignore + } + return false; + }; + + const applyStartupWindowPreference = (attempt: number) => { + if (startupWindowTimer !== null) { + window.clearTimeout(startupWindowTimer); + } + startupWindowTimer = window.setTimeout(() => { + if (cancelled) { + return; + } + if (!useStore.getState().startupFullscreen) { + return; + } + Promise.resolve() + .then(async () => { + if (await checkStartupPreferenceApplied()) { + return; + } + // 优先尝试全屏,若当前平台/时机不生效,后续走最大化兜底。 + WindowFullscreen(); + await new Promise((resolve) => window.setTimeout(resolve, settleDelayMs)); + if (await checkStartupPreferenceApplied()) { + return; + } + WindowMaximise(); + await new Promise((resolve) => window.setTimeout(resolve, settleDelayMs)); + if (await checkStartupPreferenceApplied()) { + return; + } + if (attempt < maxApplyAttempts) { + applyStartupWindowPreference(attempt + 1); + } + }); + }, 300); + }; + + if (useStore.persist.hasHydrated()) { + applyStartupWindowPreference(1); + } + const unsubscribeHydration = useStore.persist.onFinishHydration(() => { + if (cancelled) { + return; + } + applyStartupWindowPreference(1); + }); + + return () => { + cancelled = true; + if (startupWindowTimer !== null) { + window.clearTimeout(startupWindowTimer); + } + unsubscribeHydration(); + }; + }, []); + // Background Helper const getBg = (darkHex: string, lightHex: string) => { if (!darkMode) return `rgba(255, 255, 255, ${effectiveOpacity})`; // Light mode usually white @@ -915,6 +995,16 @@ function App() { )} +
+
启动窗口
+
+ 启动时全屏 + setStartupFullscreen(checked)} /> +
+
+ * 修改后下次启动生效 +
+
diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 5dd95a1..b1dddbd 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -3,6 +3,7 @@ import { persist } from 'zustand/middleware'; import { ConnectionConfig, SavedConnection, TabData, SavedQuery } from './types'; const DEFAULT_APPEARANCE = { opacity: 1.0, blur: 0 }; +const DEFAULT_STARTUP_FULLSCREEN = false; const LEGACY_DEFAULT_OPACITY = 0.95; const OPACITY_EPSILON = 1e-6; const MAX_URI_LENGTH = 4096; @@ -295,6 +296,7 @@ interface AppState { savedQueries: SavedQuery[]; theme: 'light' | 'dark'; appearance: { opacity: number; blur: number }; + startupFullscreen: boolean; sqlFormatOptions: { keywordCase: 'upper' | 'lower' }; queryOptions: QueryOptions; sqlLogs: SqlLog[]; @@ -321,6 +323,7 @@ interface AppState { setTheme: (theme: 'light' | 'dark') => void; setAppearance: (appearance: Partial<{ opacity: number; blur: number }>) => void; + setStartupFullscreen: (enabled: boolean) => void; setSqlFormatOptions: (options: { keywordCase: 'upper' | 'lower' }) => void; setQueryOptions: (options: Partial) => void; @@ -409,6 +412,10 @@ const sanitizeAppearance = ( return nextAppearance; }; +const sanitizeStartupFullscreen = (value: unknown): boolean => { + return value === true; +}; + const unwrapPersistedAppState = (persistedState: unknown): Record => { if (!persistedState || typeof persistedState !== 'object') { return {}; @@ -430,6 +437,7 @@ export const useStore = create()( savedQueries: [], theme: 'light', appearance: { ...DEFAULT_APPEARANCE }, + startupFullscreen: DEFAULT_STARTUP_FULLSCREEN, sqlFormatOptions: { keywordCase: 'upper' }, queryOptions: { maxRows: 5000, showColumnComment: true, showColumnType: true }, sqlLogs: [], @@ -541,6 +549,7 @@ export const useStore = create()( setTheme: (theme) => set({ theme }), setAppearance: (appearance) => set((state) => ({ appearance: { ...state.appearance, ...appearance } })), + setStartupFullscreen: (enabled) => set({ startupFullscreen: !!enabled }), setSqlFormatOptions: (options) => set({ sqlFormatOptions: options }), setQueryOptions: (options) => set((state) => ({ queryOptions: { ...state.queryOptions, ...options } })), @@ -578,6 +587,7 @@ export const useStore = create()( nextState.savedQueries = sanitizeSavedQueries(state.savedQueries); nextState.theme = sanitizeTheme(state.theme); nextState.appearance = sanitizeAppearance(state.appearance, version); + nextState.startupFullscreen = sanitizeStartupFullscreen(state.startupFullscreen); nextState.sqlFormatOptions = sanitizeSqlFormatOptions(state.sqlFormatOptions); nextState.queryOptions = sanitizeQueryOptions(state.queryOptions); nextState.tableAccessCount = sanitizeTableAccessCount(state.tableAccessCount); @@ -593,6 +603,7 @@ export const useStore = create()( savedQueries: sanitizeSavedQueries(state.savedQueries), theme: sanitizeTheme(state.theme), appearance: sanitizeAppearance(state.appearance, 3), + startupFullscreen: sanitizeStartupFullscreen(state.startupFullscreen), sqlFormatOptions: sanitizeSqlFormatOptions(state.sqlFormatOptions), queryOptions: sanitizeQueryOptions(state.queryOptions), tableAccessCount: sanitizeTableAccessCount(state.tableAccessCount), @@ -604,6 +615,7 @@ export const useStore = create()( savedQueries: state.savedQueries, theme: state.theme, appearance: state.appearance, + startupFullscreen: state.startupFullscreen, sqlFormatOptions: state.sqlFormatOptions, queryOptions: state.queryOptions, tableAccessCount: state.tableAccessCount,