diff --git a/lang/index.json b/lang/index.json index f5d1db5..b0ee938 100644 --- a/lang/index.json +++ b/lang/index.json @@ -3894,5 +3894,53 @@ "ru": "", "en": "", "fr": "" + }, + "70wovq6": { + "zh-cn": "窗口默认尺寸", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" + }, + "j6rndol": { + "zh-cn": "设置 Milkup 启动时的窗口宽度和高度", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" + }, + "hdojy66": { + "zh-cn": "宽度(PX)", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" + }, + "c2mhr16": { + "zh-cn": "最小 400", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" + }, + "va60g76": { + "zh-cn": "高度(PX)", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" + }, + "c2mihq6": { + "zh-cn": "最小 300", + "ja": "", + "ko": "", + "ru": "", + "en": "", + "fr": "" } } diff --git a/src/main/index.ts b/src/main/index.ts index de5d6df..aa07b59 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -31,8 +31,9 @@ async function createWindow() { win = new BrowserWindow({ width: 1200, height: 800, - minWidth: 800, - minHeight: 600, + minWidth: 400, + minHeight: 300, + show: false, frame: false, titleBarStyle: "hidden", // ✅ macOS 专属 icon: path.join(__dirname, "../assets/icons/milkup.ico"), @@ -258,6 +259,20 @@ app.whenReady().then(async () => { } }); + let startupSizeTimeout: ReturnType | null = null; + ipcMain.on("window:apply-startup-size", (event, width: number, height: number) => { + const senderWin = BrowserWindow.fromWebContents(event.sender); + if (senderWin !== win) return; + if (!win || win.isDestroyed()) return; + if (startupSizeTimeout) { + clearTimeout(startupSizeTimeout); + startupSizeTimeout = null; + } + win.setSize(width, height); + win.center(); + win.show(); + }); + // 监听渲染进程就绪事件 (Moved up to avoid race condition) ipcMain.on("renderer-ready", () => { isRendererReady = true; @@ -269,6 +284,15 @@ app.whenReady().then(async () => { await createWindow(); + if (win && !win.isDestroyed()) { + startupSizeTimeout = setTimeout(() => { + if (win && !win.isDestroyed() && !win.isVisible()) { + win.center(); + win.show(); + } + }, 2000); + } + sendLaunchFileIfExists(); }); diff --git a/src/main/ipcBridge.ts b/src/main/ipcBridge.ts index e85330d..a318bea 100644 --- a/src/main/ipcBridge.ts +++ b/src/main/ipcBridge.ts @@ -830,7 +830,10 @@ export function registerGlobalIpcHandlers() { ): Promise => { try { const sourceWin = BrowserWindow.fromWebContents(event.sender); - startDragFollow(tabData, screenX, screenY, offsetX, offsetY, sourceWin); + const bounds = sourceWin?.getBounds(); + const width = bounds?.width ?? 1000; + const height = bounds?.height ?? 700; + startDragFollow(tabData, screenX, screenY, offsetX, offsetY, sourceWin, width, height); return true; } catch (error) { console.error("[tab:tear-off-start] 创建窗口失败:", error); diff --git a/src/main/windowManager.ts b/src/main/windowManager.ts index 68e8104..b7c7a8f 100644 --- a/src/main/windowManager.ts +++ b/src/main/windowManager.ts @@ -158,8 +158,8 @@ export async function createEditorWindow( const winOptions: Electron.BrowserWindowConstructorOptions = { width, height, - minWidth: 800, - minHeight: 600, + minWidth: 400, + minHeight: 300, frame: false, titleBarStyle: "hidden", show: !fastCreate, // 拖拽跟随窗口初始不显示,避免抢夺焦点 @@ -416,7 +416,9 @@ export function startDragFollow( screenY: number, offsetX: number, offsetY: number, - sourceWin: BrowserWindow | null + sourceWin: BrowserWindow | null, + width: number, + height: number ): void { cleanupDragFollow(); @@ -429,6 +431,8 @@ export function startDragFollow( const win = await createEditorWindow({ x: initX, y: initY, + width, + height, tabData, fastCreate: true, center: false, diff --git a/src/preload.ts b/src/preload.ts index 0228e9e..273a199 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -112,6 +112,8 @@ contextBridge.exposeInMainWorld("electronAPI", { saveCustomTheme: (theme: any) => ipcRenderer.send("save-custom-theme", theme), platform: process.platform, rendererReady: () => ipcRenderer.send("renderer-ready"), + applyStartupSize: (width: number, height: number) => + ipcRenderer.send("window:apply-startup-size", width, height), // Tab 拖拽分离 tearOffTabStart: ( diff --git a/src/renderer/App.vue b/src/renderer/App.vue index f333628..6cf2ae1 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -138,6 +138,8 @@ function onOutlineTransitionEnd(e: TransitionEvent) { } onMounted(() => { + const { windowDefaultWidth = 1200, windowDefaultHeight = 800 } = config.value.other ?? {}; + window.electronAPI.applyStartupSize(windowDefaultWidth, windowDefaultHeight); initTheme(); initFont(); initOtherConfig(); @@ -275,7 +277,7 @@ const handleInstall = async () => { position: absolute; left: 0; top: 0; - width: 25%; + width: max(25%, 150px); height: 100%; z-index: 10; transform: translateX(-100%); @@ -300,7 +302,7 @@ const handleInstall = async () => { pointer-events: auto; } .editorBox { - transform: translateX(25%); + transform: translateX(max(25%, 150px)); } } @@ -332,7 +334,7 @@ const handleInstall = async () => { } .editorBox { width: 100%; - transform: translateX(25%); + transform: translateX(max(25%, 150px)); transition: none; } } diff --git a/src/renderer/components/menu/MenuBar.vue b/src/renderer/components/menu/MenuBar.vue index 6089746..331a89e 100644 --- a/src/renderer/components/menu/MenuBar.vue +++ b/src/renderer/components/menu/MenuBar.vue @@ -67,7 +67,7 @@ checkUpdate().then((updateInfo) => { @click="option.action" > - {{ option.label }} + {{ option.label }}
@@ -118,6 +118,7 @@ checkUpdate().then((updateInfo) => { gap: 4px; -webkit-app-region: drag; background: var(--background-color); + transition: width 0.25s ease; .menu-option { cursor: pointer; @@ -132,12 +133,21 @@ checkUpdate().then((updateInfo) => { background: transparent; text-align: left; color: var(--text-color); + transition: padding 0.25s ease; .menu-option-icon { font-size: 18px; flex-shrink: 0; } + .menu-option-label { + white-space: nowrap; + overflow: hidden; + transition: + opacity 0.25s ease, + max-width 0.25s ease; + } + &:hover { background: var(--hover-color); } @@ -148,5 +158,21 @@ checkUpdate().then((updateInfo) => { } } } + + @media (max-width: 600px) { + .optionsContainer { + width: 56px; + + .menu-option { + padding: 16px 19px; + gap: 0; + + .menu-option-label { + opacity: 0; + max-width: 0; + } + } + } + } } diff --git a/src/renderer/components/settings/OtherSetting.vue b/src/renderer/components/settings/OtherSetting.vue index c634d2b..b243f08 100644 --- a/src/renderer/components/settings/OtherSetting.vue +++ b/src/renderer/components/settings/OtherSetting.vue @@ -10,6 +10,7 @@ const { config } = useConfig(); const paddingSettingsExpanded = ref(false); const mermaidSettingsExpanded = ref(false); +const windowSizeExpanded = ref(false); // 从完整值中提取数字部分用于显示(如 "20px" -> "20") const displayPaddingValue = computed(() => { @@ -45,6 +46,32 @@ function setMermaidMode(mode: string) { mermaid: { ...config.value.mermaid, defaultDisplayMode: mode as "code" | "mixed" | "diagram" }, }; } + +function toggleWindowSize() { + windowSizeExpanded.value = !windowSizeExpanded.value; +} + +const currentWindowWidth = computed(() => String(config.value.other?.windowDefaultWidth ?? 1200)); +const currentWindowHeight = computed(() => String(config.value.other?.windowDefaultHeight ?? 800)); + +const WINDOW_MIN_WIDTH = 400; +const WINDOW_MIN_HEIGHT = 300; + +function handleWindowWidthChange(value: string) { + const num = Math.max(WINDOW_MIN_WIDTH, parseInt(value) || WINDOW_MIN_WIDTH); + config.value = { + ...config.value, + other: { ...config.value.other, windowDefaultWidth: num }, + }; +} + +function handleWindowHeightChange(value: string) { + const num = Math.max(WINDOW_MIN_HEIGHT, parseInt(value) || WINDOW_MIN_HEIGHT); + config.value = { + ...config.value, + other: { ...config.value.other, windowDefaultHeight: num }, + }; +} -