feat: 窗口尺寸位置与上次目录记忆、状态栏行数显示

This commit is contained in:
2026-08-18 12:39:26 +08:00
parent 3e3f9b173e
commit 6f7df57fba
6 changed files with 278 additions and 7 deletions
+50 -4
View File
@@ -1,14 +1,44 @@
import { join } from 'path'
import { app, shell, BrowserWindow, Menu, ipcMain, dialog, clipboard, nativeTheme } from 'electron'
import { join, dirname } from 'path'
import { app, shell, BrowserWindow, Menu, ipcMain, dialog, clipboard, nativeTheme, screen } from 'electron'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import iconv from 'iconv-lite'
import fs from 'fs'
import { parseState, clampBounds, type AppState } from './windowState'
/** 应用状态持久化文件(窗口 bounds + 上次文件目录),位于 userData 目录 */
const stateFile = (): string => join(app.getPath('userData'), 'window-state.json')
/** 当前生效的应用状态(启动时从磁盘读取,运行中增量更新) */
let appState: AppState = {}
/** 读取持久化状态:文件不存在/损坏一律回退空状态,不影响启动 */
function loadState(): AppState {
try {
return parseState(fs.readFileSync(stateFile(), 'utf-8'))
} catch {
return {}
}
}
/** 写回持久化状态:失败静默忽略(目录只读/磁盘满等,偏好记忆失效但不影响本次会话) */
function saveState(): void {
try {
fs.writeFileSync(stateFile(), JSON.stringify(appState), 'utf-8')
} catch {
// 忽略写入失败
}
}
function createWindow(): void {
// 恢复上次窗口尺寸/位置:越界 bounds 钳制回当前屏幕工作区,无记录时用默认尺寸
const wa = screen.getPrimaryDisplay().workArea
const bounds = appState.bounds ? clampBounds(appState.bounds, wa) : null
// 主窗口
const mainWindow = new BrowserWindow({
width: 1280,
height: 820,
width: bounds?.width ?? 1280,
height: bounds?.height ?? 820,
x: bounds?.x,
y: bounds?.y,
minWidth: 900,
minHeight: 600,
show: false,
@@ -24,6 +54,14 @@ function createWindow(): void {
}
})
// 关闭时记忆窗口普通状态 bounds(最大化/全屏/最小化时不记录,避免把超屏尺寸存进偏好)
mainWindow.on('close', () => {
if (mainWindow.isMinimized() || mainWindow.isMaximized() || mainWindow.isFullScreen()) return
const b = mainWindow.getBounds()
appState.bounds = { x: b.x, y: b.y, width: b.width, height: b.height }
saveState()
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
@@ -98,6 +136,8 @@ function decodeText(buf: Buffer): { text: string; encoding: string; binary: bool
ipcMain.handle('file:open', async (_event, side: 'left' | 'right' | null) => {
const result = await dialog.showOpenDialog({
title: `选择${side === 'left' ? '左侧' : side === 'right' ? '右侧' : ''}文本文件`,
// 记忆上次成功打开文件所在目录;无记录时传 undefined 走系统默认(最近使用位置)
defaultPath: appState.lastDir,
properties: ['openFile'],
filters: [
// 与渲染进程 TEXT_EXTENSIONSsrc/renderer/src/diff/textUtils.ts)完全对齐;两进程无法共享模块,改动需双向同步
@@ -108,6 +148,9 @@ ipcMain.handle('file:open', async (_event, side: 'left' | 'right' | null) => {
})
if (result.canceled || result.filePaths.length === 0) return null
const filePath = result.filePaths[0]
// 记录所在目录供下次对话框起始定位(与窗口 bounds 同存一个状态文件)
appState.lastDir = dirname(filePath)
saveState()
const name = filePath.split(/[\\/]/).pop() ?? filePath
let buf: Buffer
try {
@@ -163,6 +206,9 @@ app.whenReady().then(() => {
// 强制暗色系统主题:Windows 标题栏/滚动条等系统控件跟随应用暗色科幻风格
nativeTheme.themeSource = 'dark'
// 读取持久化应用状态(窗口 bounds + 上次文件目录;损坏数据回退空状态)
appState = loadState()
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})