diff --git a/src/main/bootstrap/main-window.ts b/src/main/bootstrap/main-window.ts index 172b470..2f33ee3 100644 --- a/src/main/bootstrap/main-window.ts +++ b/src/main/bootstrap/main-window.ts @@ -1,6 +1,5 @@ import { BrowserWindow, app, shell } from 'electron' import { join } from 'path' -import { is } from '@electron-toolkit/utils' import icon from '../../../resources/icon.png?asset' import { fileURLToPath } from 'url' import { dirname } from 'path' @@ -8,6 +7,10 @@ import { dirname } from 'path' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) +function isDevelopment(): boolean { + return Boolean(process.env['ELECTRON_RENDERER_URL']) || process.env.NODE_ENV === 'development' +} + export function createMainWindow(): BrowserWindow { const mainWindow = new BrowserWindow({ width: 1200, @@ -32,7 +35,7 @@ export function createMainWindow(): BrowserWindow { return { action: 'deny' } }) - if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + if (isDevelopment() && process.env['ELECTRON_RENDERER_URL']) { mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) } else { mainWindow.loadFile(join(__dirname, '../renderer/index.html')) diff --git a/src/main/bootstrap/runtime.ts b/src/main/bootstrap/runtime.ts index 34b0a39..c441980 100644 --- a/src/main/bootstrap/runtime.ts +++ b/src/main/bootstrap/runtime.ts @@ -1,5 +1,4 @@ import { app, dialog } from 'electron' -import { electronApp, optimizer } from '@electron-toolkit/utils' import fs from 'fs' import { join } from 'path' import { ConfigManager } from '../services/config/config-manager' @@ -12,10 +11,23 @@ export function configurePlaywrightBrowsersPath(): string { } export function setupElectronRuntime(): void { - electronApp.setAppUserModelId('com.electron') + app.setAppUserModelId('com.electron') app.on('browser-window-created', (_, window) => { - optimizer.watchWindowShortcuts(window) + window.webContents.on('before-input-event', (event, input) => { + const isReloadShortcut = (input.control || input.meta) && input.key.toLowerCase() === 'r' + const isToggleDevTools = input.key === 'F12' + + if (!app.isPackaged && isToggleDevTools && input.type === 'keyDown') { + window.webContents.toggleDevTools() + event.preventDefault() + return + } + + if (app.isPackaged && isReloadShortcut) { + event.preventDefault() + } + }) }) } diff --git a/src/main/index.ts b/src/main/index.ts index ee08f1e..2d34fb4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,12 +8,10 @@ import { } from './bootstrap/runtime' import { setupProcessGuards } from './bootstrap/process-guards' -const playwrightBrowsersPath = configurePlaywrightBrowsersPath() - -setupProcessGuards() -registerMainWindowLifecycle() - app.whenReady().then(async () => { + setupProcessGuards() + registerMainWindowLifecycle() + const playwrightBrowsersPath = configurePlaywrightBrowsersPath() ensurePlaywrightRuntime(playwrightBrowsersPath) await initializeMainProcessServices() setupElectronRuntime() diff --git a/src/main/services/auth/auth-application-service.ts b/src/main/services/auth/auth-application-service.ts index edfcc05..209701a 100644 --- a/src/main/services/auth/auth-application-service.ts +++ b/src/main/services/auth/auth-application-service.ts @@ -15,6 +15,8 @@ import type { const log = createLogger('AuthApplicationService') export class AuthApplicationService { + private silentLoginPromise: Promise | null = null + constructor( private readonly sessionManager: SessionManager = SessionManager.getInstance(), private readonly updateService: UpdateService = UpdateService.getInstance() @@ -25,6 +27,20 @@ export class AuthApplicationService { } async silentLogin(): Promise { + if (this.silentLoginPromise) { + log.debug('Reusing in-flight silent login request') + return this.silentLoginPromise + } + + this.silentLoginPromise = this.performSilentLogin() + try { + return await this.silentLoginPromise + } finally { + this.silentLoginPromise = null + } + } + + private async performSilentLogin(): Promise { log.info('Attempting silent login') const success = await this.sessionManager.loginByComputerName() const userInfo = this.sessionManager.getUserInfo() diff --git a/src/main/services/config/config-manager.ts b/src/main/services/config/config-manager.ts index b4a3a4c..bd05f81 100644 --- a/src/main/services/config/config-manager.ts +++ b/src/main/services/config/config-manager.ts @@ -133,7 +133,7 @@ export class ConfigManager { if (this.initialized) return // 检测是否为开发环境 - const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged + const isDev = process.env.NODE_ENV === 'development' || !(app?.isPackaged ?? false) if (isDev) { // 开发环境:配置文件放在项目根目录,方便编辑和调试 diff --git a/src/main/services/logger/index.ts b/src/main/services/logger/index.ts index ed577f8..6764bca 100644 --- a/src/main/services/logger/index.ts +++ b/src/main/services/logger/index.ts @@ -115,7 +115,7 @@ export function setLogLevel(level: string): void { } // Add error-specific file transport in production -if (app.isPackaged) { +if (app?.isPackaged) { logger.add( new DailyRotateFile({ filename: path.join(getLogDir(), 'error-%DATE%.log'), diff --git a/src/main/services/update/update-service.ts b/src/main/services/update/update-service.ts index 296fe54..a8f3762 100644 --- a/src/main/services/update/update-service.ts +++ b/src/main/services/update/update-service.ts @@ -44,7 +44,7 @@ function appendPortableLaunchLog( } function getCurrentAppVersion(): string { - return typeof app.getVersion === 'function' ? app.getVersion() : '0.0.0' + return typeof app?.getVersion === 'function' ? app.getVersion() : '0.0.0' } function getCurrentChannel(): ReleaseChannel { @@ -79,7 +79,7 @@ function getSupportState(config: UpdateConfig | null): { return { supported: false, reason: '当前仅支持 Windows 自动更新' } } - if (app.isPackaged) { + if (app?.isPackaged) { return { supported: true } } diff --git a/src/main/services/user/session-manager.ts b/src/main/services/user/session-manager.ts index 03c5b76..aba523b 100644 --- a/src/main/services/user/session-manager.ts +++ b/src/main/services/user/session-manager.ts @@ -43,9 +43,10 @@ export class SessionManager { * @returns True if login successful, false otherwise */ public async login(username: string, password: string): Promise { + let dao: InstanceType<(typeof import('./bip-users-dao'))['BIPUsersDAO']> | null = null try { const { BIPUsersDAO } = await import('./bip-users-dao') - const dao = new BIPUsersDAO() + dao = new BIPUsersDAO() const userInfo = await dao.authenticate(username, password) if (userInfo) { @@ -60,6 +61,12 @@ export class SessionManager { } catch (error) { console.error('[SessionManager] Login error:', error) return false + } finally { + if (dao) { + await dao.disconnect().catch((error) => { + console.error('[SessionManager] Login disconnect error:', error) + }) + } } } @@ -68,10 +75,11 @@ export class SessionManager { * @returns True if login successful, false otherwise */ public async loginByComputerName(): Promise { + let dao: InstanceType<(typeof import('./bip-users-dao'))['BIPUsersDAO']> | null = null try { const { BIPUsersDAO } = await import('./bip-users-dao') const { hostname } = await import('os') - const dao = new BIPUsersDAO() + dao = new BIPUsersDAO() const computerName = hostname() const userInfo = await dao.authenticateByComputerName(computerName) @@ -87,6 +95,12 @@ export class SessionManager { } catch (error) { console.error('[SessionManager] Silent login error:', error) return false + } finally { + if (dao) { + await dao.disconnect().catch((error) => { + console.error('[SessionManager] Silent login disconnect error:', error) + }) + } } } @@ -174,13 +188,20 @@ export class SessionManager { * Get all users from database (for Admin user selection) */ public async getAllUsers(): Promise { + let dao: InstanceType<(typeof import('./bip-users-dao'))['BIPUsersDAO']> | null = null try { const { BIPUsersDAO } = await import('./bip-users-dao') - const dao = new BIPUsersDAO() + dao = new BIPUsersDAO() return await dao.getAllUsers() } catch (error) { console.error('[SessionManager] Get all users error:', error) return [] + } finally { + if (dao) { + await dao.disconnect().catch((error) => { + console.error('[SessionManager] Get all users disconnect error:', error) + }) + } } } } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index d85b8bd..cd389f9 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -70,6 +70,7 @@ function App(): React.JSX.Element { const [updateStatus, setUpdateStatus] = useState(null) const [updateCatalog, setUpdateCatalog] = useState(null) const [showUpdateDialog, setShowUpdateDialog] = useState(false) + const authInitializationStartedRef = React.useRef(false) // Load error message from sessionStorage const showError = (message: string) => { @@ -145,6 +146,12 @@ function App(): React.JSX.Element { // Initialize authentication on mount useEffect(() => { + if (authInitializationStartedRef.current) { + logger.debug('Skipping duplicate auth initialization effect') + return + } + + authInitializationStartedRef.current = true logger.info('=== Initializing auth... ===') void initializeAuth() }, [initializeAuth, logger]) diff --git a/src/renderer/src/hooks/useLogger.ts b/src/renderer/src/hooks/useLogger.ts index 6ede348..88efcfc 100644 --- a/src/renderer/src/hooks/useLogger.ts +++ b/src/renderer/src/hooks/useLogger.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import type { LogLevel } from '../../../shared/ipc-channels' /** @@ -70,33 +70,24 @@ export function useLogger(context: string): RendererLogger { ) // Return memoized logger methods - return { - log: logger, - info: useCallback( - (message: string, meta?: Record) => { + return useMemo( + () => ({ + log: logger, + info: (message: string, meta?: Record) => { logger('info', message, meta) }, - [logger] - ), - warn: useCallback( - (message: string, meta?: Record) => { + warn: (message: string, meta?: Record) => { logger('warn', message, meta) }, - [logger] - ), - error: useCallback( - (message: string, meta?: Record) => { + error: (message: string, meta?: Record) => { logger('error', message, meta) }, - [logger] - ), - debug: useCallback( - (message: string, meta?: Record) => { + debug: (message: string, meta?: Record) => { logger('debug', message, meta) - }, - [logger] - ) - } + } + }), + [logger] + ) } /**