From 2baf55bd2ebfc2f657ec29e475d6386f3c4ef335 Mon Sep 17 00:00:00 2001 From: Misaka Date: Sat, 21 Mar 2026 19:05:10 +0800 Subject: [PATCH] refactor: split app bootstrap and shell --- src/renderer/src/App.tsx | 624 ++---------------- .../components/app/AuthenticatedAppShell.tsx | 209 ++++++ .../src/components/app/UnauthenticatedApp.tsx | 142 ++++ src/renderer/src/hooks/useAppBootstrap.ts | 312 +++++++++ 4 files changed, 725 insertions(+), 562 deletions(-) create mode 100644 src/renderer/src/components/app/AuthenticatedAppShell.tsx create mode 100644 src/renderer/src/components/app/UnauthenticatedApp.tsx create mode 100644 src/renderer/src/hooks/useAppBootstrap.ts diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index cd389f9..fc59b79 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,578 +1,78 @@ -/** - * ERP App - Main application with authentication - * - * Mimics the Python ERPApp functionality: - * - Silent login by computer name on startup - * - Show login dialog if silent login fails - * - Show user selection dialog for Admin users - * - Display main content after successful authentication - */ - -import React, { useCallback, useEffect, useState } from 'react' -import { - LayoutDashboard, - Download, - Trash2, - Settings, - Database, - User, - LogOut, - ArrowUpCircle, - LoaderCircle -} from 'lucide-react' -import { useLogger } from './hooks/useLogger' -import LoginDialog from './components/LoginDialog' -import UserSelectionDialog, { - type UserInfo as SelectedUserInfo -} from './components/UserSelectionDialog' -import { Toast } from './components/ui/Toast' -import ExtractorPage from './pages/ExtractorPage' -import CleanerPage from './pages/CleanerPage' -import SettingsPage from './pages/SettingsPage' -import UpdateDialog from './components/UpdateDialog' -import type { - DownloadReleaseRequest, - UpdateDialogCatalog, - UpdateStatus -} from '../../main/types/update.types' - -type Page = 'home' | 'extractor' | 'cleaner' | 'settings' - -interface CurrentUser { - username: string - userType: 'Admin' | 'User' | 'Guest' -} +import React from 'react' +import { AuthenticatedAppShell } from './components/app/AuthenticatedAppShell' +import { UnauthenticatedApp } from './components/app/UnauthenticatedApp' +import { useAppBootstrap } from './hooks/useAppBootstrap' function App(): React.JSX.Element { - // Create logger instance for App component - const logger = useLogger('App') - - // Authentication state - const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isAuthenticating, setIsAuthenticating] = useState(true) - const [currentUser, setCurrentUser] = useState(null) - const [computerName, setComputerName] = useState('') - - // Dialog state - const [showLoginDialog, setShowLoginDialog] = useState(false) - const [showUserSelection, setShowUserSelection] = useState(false) - const [allUsers, setAllUsers] = useState([]) - const [errorMessage, setErrorMessage] = useState('') - - // Track if current session is switched by Admin - const [isSwitchedByAdmin, setIsSwitchedByAdmin] = useState(false) - - // Ref for logout button (for focus restoration) const logoutButtonRef = React.useRef(null) - // Navigation state - const [currentPage, setCurrentPage] = useState('extractor') // Default to extractor for the new layout - const [updateStatus, setUpdateStatus] = useState(null) - const [updateCatalog, setUpdateCatalog] = useState(null) - const [showUpdateDialog, setShowUpdateDialog] = useState(false) - const authInitializationStartedRef = React.useRef(false) + const { + isAuthenticated, + isAuthenticating, + currentUser, + computerName, + showLoginDialog, + showUserSelection, + allUsers, + errorMessage, + isSwitchedByAdmin, + currentPage, + setCurrentPage, + updateStatus, + updateCatalog, + showUpdateDialog, + setShowUpdateDialog, + showError, + handleLogin, + handleLoginCancel, + handleUserSelect, + handleUserSelectionCancel, + handleLogout, + openUpdateDialog, + handleInstallUserRelease, + handleAdminDownloadAndInstall, + refreshUpdateDialogState + } = useAppBootstrap() - // Load error message from sessionStorage - const showError = (message: string) => { - setErrorMessage(message) - setTimeout(() => setErrorMessage(''), 3000) - } - - const refreshUpdateState = useCallback(async () => { - const result = await window.electron.update.getStatus() - if (result.success && result.data) { - setUpdateStatus(result.data) - } - }, []) - - const refreshUpdateCatalog = useCallback(async () => { - const result = await window.electron.update.getCatalog() - if (result.success && result.data) { - setUpdateCatalog(result.data) - } - }, []) - - const initializeAuth = useCallback(async () => { - logger.info('=== Starting initializeAuth ===') - try { - // Get computer name - logger.debug('Getting computer name...') - const computerNameResult = await window.electron.auth.getComputerName() - const name = - computerNameResult.success && computerNameResult.data ? computerNameResult.data : '' - logger.debug('Computer name obtained', { name }) - setComputerName(name) - - // Try silent login - logger.debug('Trying silent login...') - const silentLoginResult = await window.electron.auth.silentLogin() - const result = silentLoginResult.data - logger.debug('Silent login result', { result }) - - if (silentLoginResult.success && result?.success && result.userInfo) { - logger.info('Silent login success', { username: result.userInfo.username }) - setCurrentUser({ - username: result.userInfo.username, - userType: result.userInfo.userType - }) - - // Check if admin needs user selection - if (result.requiresUserSelection) { - logger.info('Admin user needs to select user') - // Load all users for selection - const usersResult = await window.electron.auth.getAllUsers() - setAllUsers(usersResult.success && usersResult.data ? usersResult.data : []) - setShowUserSelection(true) - } else { - logger.info('Setting authenticated to true') - setIsAuthenticated(true) - } - } else { - logger.info('Silent login failed, showing login dialog') - // Silent login failed, show login dialog - setShowLoginDialog(true) - } - } catch (error) { - logger.error('Auth initialization error', { - error: error instanceof Error ? error.message : String(error) - }) - setShowLoginDialog(true) - } finally { - logger.debug('Setting isAuthenticating to false') - setIsAuthenticating(false) - } - logger.info('=== Auth initialization complete ===') - }, [logger]) - - // 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]) - - useEffect(() => { - const unsubscribe = window.electron.update.onStatusChanged((status) => { - setUpdateStatus(status) - if ( - status.phase === 'available' || - status.phase === 'downloaded' || - status.phase === 'idle' - ) { - void refreshUpdateCatalog() - } - }) - - void refreshUpdateState() - - return unsubscribe - }, [refreshUpdateCatalog, refreshUpdateState]) - - useEffect(() => { - if (isAuthenticated) { - void refreshUpdateState() - void refreshUpdateCatalog() - return - } - - setUpdateStatus(null) - setUpdateCatalog(null) - setShowUpdateDialog(false) - }, [isAuthenticated, refreshUpdateCatalog, refreshUpdateState]) - - // Handle login dialog submit - const handleLogin = async (username: string, password: string): Promise => { - try { - const result = await window.electron.auth.login({ username, password }) - const loginData = result.success ? result.data : undefined - - if (result.success && loginData?.userInfo) { - setCurrentUser({ - username: loginData.userInfo.username, - userType: loginData.userInfo.userType - }) - - // Check if admin needs user selection - if (loginData.userInfo.userType === 'Admin') { - setShowLoginDialog(false) - // Load all users for selection - const usersResult = await window.electron.auth.getAllUsers() - setAllUsers(usersResult.success && usersResult.data ? usersResult.data : []) - setShowUserSelection(true) - } else { - setIsAuthenticated(true) - setShowLoginDialog(false) - } - return true - } - return false - } catch (error) { - logger.error('Login error', { error: error instanceof Error ? error.message : String(error) }) - return false - } - } - - // Handle login dialog cancel - const handleLoginCancel = () => { - // User cancelled, keep showing dialog or exit - setShowLoginDialog(false) - } - - // Handle user selection - const handleUserSelect = async (user: SelectedUserInfo) => { - try { - const result = await window.electron.auth.switchUser(user) - const switchData = result.success ? result.data : undefined - if (result.success && switchData) { - setCurrentUser({ - username: switchData.userInfo?.username || user.username, - userType: switchData.userInfo?.userType || user.userType - }) - setIsAuthenticated(true) - setShowUserSelection(false) - // Mark as switched by Admin - setIsSwitchedByAdmin(true) - } - } catch (error) { - logger.error('User selection error', { - error: error instanceof Error ? error.message : String(error) - }) - showError('切换用户失败') - } - } - - // Handle user selection cancel - const handleUserSelectionCancel = () => { - // User cancelled selection, logout and show login dialog - window.electron.auth.logout() - setShowUserSelection(false) - setShowLoginDialog(true) - } - - // Handle logout - const handleLogout = async () => { - await window.electron.auth.logout() - setIsAuthenticated(false) - setCurrentUser(null) - setIsSwitchedByAdmin(false) - setShowLoginDialog(true) - } - - const openUpdateDialog = async () => { - await refreshUpdateCatalog() - setShowUpdateDialog(true) - } - - const handleInstallUserRelease = async () => { - if (!updateCatalog?.recommendedRelease) { - showError('暂无可安装更新') - return - } - - if (updateStatus?.phase !== 'downloaded') { - const downloadResult = await window.electron.update.downloadRelease( - updateCatalog.recommendedRelease - ) - if (!downloadResult.success) { - showError(downloadResult.error || '下载更新失败') - return - } - } - - const installResult = await window.electron.update.installDownloaded() - if (!installResult.success) { - showError(installResult.error || '启动安装失败') - } - } - - const handleAdminDownloadAndInstall = async (release: DownloadReleaseRequest) => { - const downloadResult = await window.electron.update.downloadRelease(release) - if (!downloadResult.success) { - showError(downloadResult.error || '下载更新失败') - return - } - - const installResult = await window.electron.update.installDownloaded() - if (!installResult.success) { - showError(installResult.error || '启动安装失败') - } - } - - // Check if should show logout button - // Show logout if: user is Admin, OR user was switched by Admin const shouldShowLogout = currentUser?.userType === 'Admin' || isSwitchedByAdmin - // Show loading state during authentication - if (isAuthenticating) { - return ( -
-
-
-

认证中...

-
- -
- ) - } - - // Show login dialog if not authenticated if (!isAuthenticated) { - logger.debug('Render: not authenticated', { showLoginDialog, computerName }) return ( - <> - - - {/* User Selection Dialog for Admin */} - - - {errorMessage &&
{errorMessage}
} - - {/* If showLoginDialog is false but not authenticated, show a message */} - {!showLoginDialog && !showUserSelection && ( -
-
-

- 欢迎,{currentUser?.username}! -

-

正在加载主界面...

-
-
- )} - - - + ) } - // Show main content when authenticated - logger.debug('Render: authenticated', { currentUser, currentPage }) - - const navItems = [ - { id: 'extractor', label: '数据提取 (Extractor)', icon: }, - { id: 'cleaner', label: '物料验证与清理 (Cleaner)', icon: }, - { id: 'settings', label: '系统设置 (Settings)', icon: } - ] - - const showUpdateEntry = - !!updateStatus && - ((currentUser?.userType === 'User' && updateStatus.phase === 'downloaded') || - (currentUser?.userType === 'Admin' && - (updateStatus.adminHasAnyRelease || - updateStatus.phase === 'downloading' || - updateStatus.phase === 'downloaded' || - updateStatus.phase === 'installing'))) - - const updateButtonLabel = - currentUser?.userType === 'Admin' - ? updateStatus?.phase === 'downloading' - ? '下载更新中...' - : '有可用版本' - : updateStatus?.phase === 'downloaded' - ? `发现稳定版 V${updateStatus.latestVersion}` - : '发现新版本' - return ( -
- {/* ================= 顶部导航与标题栏 ================= */} -
-
-
-
-
-
-
- -
setCurrentPage('home')} - style={{ WebkitAppRegion: 'no-drag' } as any} - > -
- - ERP Auto -
- - {__APP_VERSION__}({__GIT_HASH__}) - -
-
- - - -
- {showUpdateEntry && ( - - )} -
- - 数据库已连接 -
-
- - - {currentUser?.username} - - {shouldShowLogout && ( - - )} -
-
-
- - {/* ================= 主体内容区域 ================= */} -
-
- {currentPage === 'home' && ( -
-
- -

欢迎使用 ERP Auto

-

- 自动化处理 ERP 系统中的数据提取和清理任务。请使用上方导航栏选择您需要的功能模块。 -

-
-
- )} - {currentPage === 'extractor' && } - {currentPage === 'cleaner' && } - {currentPage === 'settings' && } -
-
- - {/* Toast Notifications */} - - setShowUpdateDialog(false)} - onInstallUserRelease={handleInstallUserRelease} - onDownloadAndInstallAdminRelease={async (release) => handleAdminDownloadAndInstall(release)} - onRefreshCatalog={async () => { - await window.electron.update.checkNow() - await refreshUpdateCatalog() - await refreshUpdateState() - }} - /> -
+ setShowUpdateDialog(false)} + onInstallUserRelease={handleInstallUserRelease} + onDownloadAndInstallAdminRelease={handleAdminDownloadAndInstall} + onRefreshCatalog={refreshUpdateDialogState} + shouldShowLogout={shouldShowLogout} + onLogout={handleLogout} + logoutButtonRef={logoutButtonRef} + /> ) } diff --git a/src/renderer/src/components/app/AuthenticatedAppShell.tsx b/src/renderer/src/components/app/AuthenticatedAppShell.tsx new file mode 100644 index 0000000..8f94658 --- /dev/null +++ b/src/renderer/src/components/app/AuthenticatedAppShell.tsx @@ -0,0 +1,209 @@ +import React from 'react' +import { + ArrowUpCircle, + Database, + Download, + LayoutDashboard, + LoaderCircle, + LogOut, + Settings, + Trash2, + User +} from 'lucide-react' +import type { + DownloadReleaseRequest, + UpdateDialogCatalog, + UpdateStatus +} from '../../../../main/types/update.types' +import type { CurrentUser, Page } from '../../hooks/useAppBootstrap' +import UpdateDialog from '../UpdateDialog' +import { Toast } from '../ui/Toast' +import CleanerPage from '../../pages/CleanerPage' +import ExtractorPage from '../../pages/ExtractorPage' +import SettingsPage from '../../pages/SettingsPage' + +interface AuthenticatedAppShellProps { + currentUser: CurrentUser | null + currentPage: Page + onNavigate: (page: Page) => void + updateStatus: UpdateStatus | null + updateCatalog: UpdateDialogCatalog | null + showUpdateDialog: boolean + onOpenUpdateDialog: () => Promise + onCloseUpdateDialog: () => void + onInstallUserRelease: () => Promise + onDownloadAndInstallAdminRelease: (release: DownloadReleaseRequest) => Promise + onRefreshCatalog: () => Promise + shouldShowLogout: boolean + onLogout: () => Promise + logoutButtonRef: React.RefObject +} + +export function AuthenticatedAppShell({ + currentUser, + currentPage, + onNavigate, + updateStatus, + updateCatalog, + showUpdateDialog, + onOpenUpdateDialog, + onCloseUpdateDialog, + onInstallUserRelease, + onDownloadAndInstallAdminRelease, + onRefreshCatalog, + shouldShowLogout, + onLogout, + logoutButtonRef +}: AuthenticatedAppShellProps): React.JSX.Element { + const navItems = [ + { id: 'extractor' as const, label: '数据提取 (Extractor)', icon: }, + { id: 'cleaner' as const, label: '物料验证与清理 (Cleaner)', icon: }, + { id: 'settings' as const, label: '系统设置 (Settings)', icon: } + ] + + const showUpdateEntry = + !!updateStatus && + ((currentUser?.userType === 'User' && updateStatus.phase === 'downloaded') || + (currentUser?.userType === 'Admin' && + (updateStatus.adminHasAnyRelease || + updateStatus.phase === 'downloading' || + updateStatus.phase === 'downloaded' || + updateStatus.phase === 'installing'))) + + const updateButtonLabel = + currentUser?.userType === 'Admin' + ? updateStatus?.phase === 'downloading' + ? '下载更新中...' + : '有可用版本' + : updateStatus?.phase === 'downloaded' + ? `发现稳定版 V${updateStatus.latestVersion}` + : '发现新版本' + + return ( +
+
+
+
+
+
+
+
+ +
onNavigate('home')} + style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties} + > +
+ + ERP Auto +
+ + {__APP_VERSION__}({__GIT_HASH__}) + +
+
+ + + +
+ {showUpdateEntry && ( + + )} +
+ + 数据库已连接 +
+
+ + + {currentUser?.username} + + {shouldShowLogout && ( + + )} +
+
+
+ +
+
+ {currentPage === 'home' && ( +
+
+ +

欢迎使用 ERP Auto

+

+ 自动化处理 ERP 系统中的数据提取和清理任务。请使用上方导航栏选择您需要的功能模块。 +

+
+
+ )} + {currentPage === 'extractor' && } + {currentPage === 'cleaner' && } + {currentPage === 'settings' && } +
+
+ + + + + onDownloadAndInstallAdminRelease(release) + } + onRefreshCatalog={onRefreshCatalog} + /> +
+ ) +} diff --git a/src/renderer/src/components/app/UnauthenticatedApp.tsx b/src/renderer/src/components/app/UnauthenticatedApp.tsx new file mode 100644 index 0000000..0a050ee --- /dev/null +++ b/src/renderer/src/components/app/UnauthenticatedApp.tsx @@ -0,0 +1,142 @@ +import React from 'react' +import LoginDialog from '../LoginDialog' +import UserSelectionDialog, { type UserInfo as SelectedUserInfo } from '../UserSelectionDialog' + +interface CurrentUser { + username: string + userType: 'Admin' | 'User' | 'Guest' +} + +interface UnauthenticatedAppProps { + isAuthenticating: boolean + showLoginDialog: boolean + showUserSelection: boolean + computerName: string + currentUser: CurrentUser | null + allUsers: SelectedUserInfo[] + errorMessage: string + onLogin: (username: string, password: string) => Promise + onLoginCancel: () => void + onSelectUser: (user: SelectedUserInfo) => Promise + onUserSelectionCancel: () => void + onError: (message: string) => void + logoutButtonRef: React.RefObject +} + +export function UnauthenticatedApp({ + isAuthenticating, + showLoginDialog, + showUserSelection, + computerName, + currentUser, + allUsers, + errorMessage, + onLogin, + onLoginCancel, + onSelectUser, + onUserSelectionCancel, + onError, + logoutButtonRef +}: UnauthenticatedAppProps): React.JSX.Element { + if (isAuthenticating) { + return ( +
+
+
+

认证中...

+
+ +
+ ) + } + + return ( + <> + + + + + {errorMessage &&
{errorMessage}
} + + {!showLoginDialog && !showUserSelection && ( +
+
+

+ 欢迎,{currentUser?.username}! +

+

正在加载主界面...

+
+
+ )} + + + + ) +} diff --git a/src/renderer/src/hooks/useAppBootstrap.ts b/src/renderer/src/hooks/useAppBootstrap.ts new file mode 100644 index 0000000..affe16b --- /dev/null +++ b/src/renderer/src/hooks/useAppBootstrap.ts @@ -0,0 +1,312 @@ +import { startTransition, useCallback, useEffect, useRef, useState } from 'react' +import { useLogger } from './useLogger' +import type { UserType } from '../../../main/types/user.types' +import type { + DownloadReleaseRequest, + UpdateDialogCatalog, + UpdateStatus +} from '../../../main/types/update.types' + +export type Page = 'home' | 'extractor' | 'cleaner' | 'settings' + +export interface CurrentUser { + username: string + userType: UserType +} + +export interface SelectedUserInfo { + id: number + username: string + userType: 'Admin' | 'User' | 'Guest' + computerName?: string +} + +export function useAppBootstrap() { + const logger = useLogger('App') + + const [isAuthenticated, setIsAuthenticated] = useState(false) + const [isAuthenticating, setIsAuthenticating] = useState(true) + const [currentUser, setCurrentUser] = useState(null) + const [computerName, setComputerName] = useState('') + const [showLoginDialog, setShowLoginDialog] = useState(false) + const [showUserSelection, setShowUserSelection] = useState(false) + const [allUsers, setAllUsers] = useState([]) + const [errorMessage, setErrorMessage] = useState('') + const [isSwitchedByAdmin, setIsSwitchedByAdmin] = useState(false) + const [currentPage, setCurrentPage] = useState('extractor') + const [updateStatus, setUpdateStatus] = useState(null) + const [updateCatalog, setUpdateCatalog] = useState(null) + const [showUpdateDialog, setShowUpdateDialog] = useState(false) + + const authInitializationStartedRef = useRef(false) + + const showError = useCallback((message: string) => { + setErrorMessage(message) + setTimeout(() => setErrorMessage(''), 3000) + }, []) + + const refreshUpdateState = useCallback(async () => { + const result = await window.electron.update.getStatus() + const nextStatus = result.success ? result.data : undefined + if (nextStatus) { + startTransition(() => { + setUpdateStatus(nextStatus) + }) + } + }, []) + + const refreshUpdateCatalog = useCallback(async () => { + const result = await window.electron.update.getCatalog() + const nextCatalog = result.success ? result.data : undefined + if (nextCatalog) { + startTransition(() => { + setUpdateCatalog(nextCatalog) + }) + } + }, []) + + const initializeAuth = useCallback(async () => { + logger.info('=== Starting initializeAuth ===') + try { + logger.debug('Getting computer name...') + const computerNameResult = await window.electron.auth.getComputerName() + const name = + computerNameResult.success && computerNameResult.data ? computerNameResult.data : '' + logger.debug('Computer name obtained', { name }) + setComputerName(name) + + logger.debug('Trying silent login...') + const silentLoginResult = await window.electron.auth.silentLogin() + const result = silentLoginResult.data + logger.debug('Silent login result', { result }) + + if (silentLoginResult.success && result?.success && result.userInfo) { + logger.info('Silent login success', { username: result.userInfo.username }) + setCurrentUser({ + username: result.userInfo.username, + userType: result.userInfo.userType + }) + + if (result.requiresUserSelection) { + logger.info('Admin user needs to select user') + const usersResult = await window.electron.auth.getAllUsers() + setAllUsers(usersResult.success && usersResult.data ? usersResult.data : []) + setShowUserSelection(true) + } else { + logger.info('Setting authenticated to true') + setIsAuthenticated(true) + } + } else { + logger.info('Silent login failed, showing login dialog') + setShowLoginDialog(true) + } + } catch (error) { + logger.error('Auth initialization error', { + error: error instanceof Error ? error.message : String(error) + }) + setShowLoginDialog(true) + } finally { + logger.debug('Setting isAuthenticating to false') + setIsAuthenticating(false) + } + logger.info('=== Auth initialization complete ===') + }, [logger]) + + useEffect(() => { + if (authInitializationStartedRef.current) { + logger.debug('Skipping duplicate auth initialization effect') + return + } + + authInitializationStartedRef.current = true + logger.info('=== Initializing auth... ===') + void initializeAuth() + }, [initializeAuth, logger]) + + useEffect(() => { + const unsubscribe = window.electron.update.onStatusChanged((status) => { + startTransition(() => { + setUpdateStatus(status) + }) + + if ( + status.phase === 'available' || + status.phase === 'downloaded' || + status.phase === 'idle' + ) { + void refreshUpdateCatalog() + } + }) + + void refreshUpdateState() + + return unsubscribe + }, [refreshUpdateCatalog, refreshUpdateState]) + + useEffect(() => { + if (isAuthenticated) { + void refreshUpdateState() + void refreshUpdateCatalog() + return + } + + setUpdateStatus(null) + setUpdateCatalog(null) + setShowUpdateDialog(false) + }, [isAuthenticated, refreshUpdateCatalog, refreshUpdateState]) + + const handleLogin = useCallback( + async (username: string, password: string): Promise => { + try { + const result = await window.electron.auth.login({ username, password }) + const loginData = result.success ? result.data : undefined + + if (result.success && loginData?.userInfo) { + setCurrentUser({ + username: loginData.userInfo.username, + userType: loginData.userInfo.userType + }) + + if (loginData.userInfo.userType === 'Admin') { + setShowLoginDialog(false) + const usersResult = await window.electron.auth.getAllUsers() + setAllUsers(usersResult.success && usersResult.data ? usersResult.data : []) + setShowUserSelection(true) + } else { + setIsAuthenticated(true) + setShowLoginDialog(false) + } + return true + } + return false + } catch (error) { + logger.error('Login error', { + error: error instanceof Error ? error.message : String(error) + }) + return false + } + }, + [logger] + ) + + const handleLoginCancel = useCallback(() => { + setShowLoginDialog(false) + }, []) + + const handleUserSelect = useCallback( + async (user: SelectedUserInfo) => { + try { + const result = await window.electron.auth.switchUser(user) + const switchData = result.success ? result.data : undefined + if (result.success && switchData) { + setCurrentUser({ + username: switchData.userInfo?.username || user.username, + userType: switchData.userInfo?.userType || user.userType + }) + setIsAuthenticated(true) + setShowUserSelection(false) + setIsSwitchedByAdmin(true) + } + } catch (error) { + logger.error('User selection error', { + error: error instanceof Error ? error.message : String(error) + }) + showError('切换用户失败') + } + }, + [logger, showError] + ) + + const handleUserSelectionCancel = useCallback(() => { + void window.electron.auth.logout() + setShowUserSelection(false) + setShowLoginDialog(true) + }, []) + + const handleLogout = useCallback(async () => { + await window.electron.auth.logout() + setIsAuthenticated(false) + setCurrentUser(null) + setIsSwitchedByAdmin(false) + setShowLoginDialog(true) + }, []) + + const openUpdateDialog = useCallback(async () => { + await refreshUpdateCatalog() + setShowUpdateDialog(true) + }, [refreshUpdateCatalog]) + + const handleInstallUserRelease = useCallback(async () => { + if (!updateCatalog?.recommendedRelease) { + showError('暂无可安装更新') + return + } + + if (updateStatus?.phase !== 'downloaded') { + const downloadResult = await window.electron.update.downloadRelease( + updateCatalog.recommendedRelease + ) + if (!downloadResult.success) { + showError(downloadResult.error || '下载更新失败') + return + } + } + + const installResult = await window.electron.update.installDownloaded() + if (!installResult.success) { + showError(installResult.error || '启动安装失败') + } + }, [showError, updateCatalog, updateStatus?.phase]) + + const handleAdminDownloadAndInstall = useCallback( + async (release: DownloadReleaseRequest) => { + const downloadResult = await window.electron.update.downloadRelease(release) + if (!downloadResult.success) { + showError(downloadResult.error || '下载更新失败') + return + } + + const installResult = await window.electron.update.installDownloaded() + if (!installResult.success) { + showError(installResult.error || '启动安装失败') + } + }, + [showError] + ) + + const refreshUpdateDialogState = useCallback(async () => { + await window.electron.update.checkNow() + await refreshUpdateCatalog() + await refreshUpdateState() + }, [refreshUpdateCatalog, refreshUpdateState]) + + return { + isAuthenticated, + isAuthenticating, + currentUser, + computerName, + showLoginDialog, + showUserSelection, + allUsers, + errorMessage, + isSwitchedByAdmin, + currentPage, + setCurrentPage, + updateStatus, + updateCatalog, + showUpdateDialog, + setShowUpdateDialog, + showError, + refreshUpdateState, + refreshUpdateCatalog, + handleLogin, + handleLoginCancel, + handleUserSelect, + handleUserSelectionCancel, + handleLogout, + openUpdateDialog, + handleInstallUserRelease, + handleAdminDownloadAndInstall, + refreshUpdateDialogState + } +}