// src/js/mainPage.js /** * [重构] 主页面管理器 * 使用新的核心框架系统 */ import configManager from './configManager.js'; import uiManager from './uiManager.js'; import i18n from './i18n.js'; import weatherWidget from './weatherWidget.js'; // [重构] 不再使用模态框版本的工具健康检查,已移除导入 import Application from './core/Application.js'; import ToolFramework from './core/ToolFramework.js'; import EventBus from './core/EventBus.js'; import ServiceContainer from './core/ServiceContainer.js'; // 简易 Markdown 解析器 function parseMarkdown(text) { if (!text) return ''; let html = text .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/^- (.*)$/gm, '
  • $1
  • ') .replace(/\n/g, '
    '); if (html.includes('
  • ')) { html = html.replace(/((
  • .*<\/li>)+)/s, ''); } return html; } class MainPage { // [重构] 模态框ID常量(统一管理,避免冲突) static APPEARANCE_FONT_MODAL_ID = 'appearance-font-download-modal'; constructor() { this.keySequence = ''; this.keyTimeout = null; this.isDownloadingUpdate = false; this.dbSettings = {}; this.settingsInterval = null; this.dashboardInterval = null; this.activeChartInstance = null; this.settingsIntervalHeavy = null; // [重构] 注册到应用核心 Application.registerModule('mainPage', this); Application.registerModule('uiManager', uiManager); Application.registerModule('configManager', configManager); // [重构] 注册服务 ServiceContainer.register('uiManager', uiManager, true); ServiceContainer.register('configManager', configManager, true); ServiceContainer.register('toolFramework', ToolFramework, true); ServiceContainer.register('eventBus', EventBus, true); // [重构] 注册错误处理器 Application.onError((error, context) => { if (uiManager && uiManager.showNotification) { uiManager.showNotification('发生错误', `详情已记录到日志 (${context})`, 'error'); } }); } /** * [重构] 统一初始化入口 */ async init() { try { // 使用新的Application核心初始化 await Application.init(); // 初始化主页面特定功能 await this.initializePage(); // 确保初始化完成后才导航 console.log('[MainPage] 初始化完成,准备导航到首页'); } catch (error) { console.error('主页面初始化失败:', error); Application.handleError(error, 'MainPage.init'); // 即使初始化失败,也尝试显示错误页面 const contentArea = document.getElementById('content-area'); if (contentArea) { contentArea.innerHTML = `

    初始化失败

    ${error.message || '未知错误'}

    `; } } } /** * [重构] 初始化页面特定功能 */ async initializePage() { // [重构] 页面特定初始化(Application已处理核心初始化) this.dbSettings = configManager.config?.dbSettings || {}; // 初始化天气组件 if (weatherWidget && weatherWidget.init) { const config = configManager.config || {}; weatherWidget.init(config.initWeatherData || {}); } // 绑定页面事件 this.bindWindowControls(); this.bindNavigationEvents(); this.bindThemeToggle(); this.addRippleEffectListener(); this.bindGlobalKeyListener(); // [重构] 监听应用事件(已由 Application 核心处理,无需额外绑定) // this._bindAppEvents(); // 已移除:方法不存在,事件绑定已由 Application 核心处理 // 全局点击监听器:处理所有自定义下拉菜单的关闭逻辑 document.addEventListener('click', (e) => { document.querySelectorAll('.custom-select-options.dynamic-active').forEach(openDropdown => { const wrapperId = openDropdown.id.replace('dd-options-', 'dd-wrapper-'); const trigger = document.getElementById(wrapperId); if (openDropdown && !openDropdown.contains(e.target) && (!trigger || !trigger.contains(e.target))) { openDropdown.classList.remove('dynamic-active'); } }); }); window.addEventListener('error', (event) => { const error = event.error || {}; configManager.logAction(`渲染进程发生错误: ${error.message || 'Unknown'}\nStack: ${error.stack || ''}`, 'error'); uiManager.showNotification('发生内部错误', '详情已记录到日志中', 'error'); }); // [关键修复] 使用 requestAnimationFrame 确保在下一帧(DOM完全就绪)后导航 requestAnimationFrame(() => { try { this.navigateTo('home').catch(error => { console.error('[MainPage] 导航到首页失败:', error); Application.handleError(error, 'MainPage.init.navigateTo'); }); const homeBtn = document.getElementById('home-btn'); if (homeBtn) { homeBtn.classList.add('active'); setTimeout(() => this.updateActiveNavButton(homeBtn), 100); } } catch (error) { console.error('[MainPage] 初始化导航失败:', error); Application.handleError(error, 'MainPage.init'); } }); // 监听下载进度 (复用逻辑) this.listenForDownloadProgress(); this.listenForGlobalNetworkSpeed(); this.autoCheckUpdates(); // [重构] 监听页面切换,如果健康检查正在进行,切换到后台模式 this._setupHealthCheckBackgroundMode(); configManager.logAction('主窗口初始化完成', 'system'); } /** * [重构] 设置健康检查后台模式 * 当用户切换页面时,如果健康检查正在进行,切换到后台模式 */ _setupHealthCheckBackgroundMode() { // 监听导航事件 const originalNavigateTo = this.navigateTo.bind(this); this.navigateTo = async (page, subPage) => { // 如果从工具箱切换到其他页面,且健康检查正在进行 if (window.toolboxVerificationPage && window.toolboxVerificationPage.isChecking) { // 切换到后台模式 await window.toolboxVerificationPage.continueCheckInBackground(); } // 执行原始导航 return originalNavigateTo(page, subPage); }; // 监听窗口关闭事件 window.addEventListener('beforeunload', () => { if (window.toolboxVerificationPage && window.toolboxVerificationPage.isChecking) { // 窗口关闭时,健康检查会在后台继续(如果可能) // 注意:实际上窗口关闭后无法继续,但可以保存当前进度 if (window.toolHealthChecker) { // 保存当前检查进度到数据库 const progress = window.toolHealthChecker.getProgress(); if (progress && progress.checked > 0) { // 可以在这里保存部分结果 console.log('[MainPage] 窗口关闭,保存健康检查进度:', progress); } } } }); } async loadLanguage() { try { const langConfig = await window.electronAPI.getLanguageConfig(); if(langConfig) { i18n.init(langConfig.pack, langConfig.fallback, langConfig.actual || langConfig.current); } } catch (e) { console.error("无法加载语言配置:", e); i18n.init(null, {}, 'zh-CN'); } } async autoCheckUpdates() { const lastCheck = localStorage.getItem('last_auto_update_check'); const now = Date.now(); const threeDays = 3 * 24 * 60 * 60 * 1000; if (!lastCheck || (now - parseInt(lastCheck)) > threeDays) { console.log('[Update] 开始后台自动检查更新...'); try { const result = await window.electronAPI.checkUpdates(); localStorage.setItem('last_auto_update_check', now.toString()); if (result.hasUpdate) { configManager.logAction(`自动检测到新版本: ${result.remoteVersion}`, 'update'); uiManager.showActionableNotification( '发现新版本', `V${result.remoteVersion} 现已发布,包含多项优化。`, '去更新', () => { this.navigateToSettingsForUpdate(); } ); } } catch (e) { console.warn('[Update] 自动检查更新失败:', e.message); } } } bindGlobalKeyListener() { document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; clearTimeout(this.keyTimeout); this.keySequence += e.key.toLowerCase(); if (this.keySequence.length > 5) { this.keySequence = this.keySequence.slice(-5); } if (this.keySequence.includes('vv')) { const aboutPanel = document.getElementById('about-panel'); if (aboutPanel && aboutPanel.classList.contains('active')) { this.handleSecretTrigger(true); this.keySequence = ''; } } this.keyTimeout = setTimeout(() => { this.keySequence = ''; }, 1500); }); } async handleSecretTrigger(skipValidation = false) { const access = await window.electronAPI.checkSecretAccess(); const showLockoutMessage = (until) => { const timeLeftMs = until - Date.now(); const minutes = Math.ceil(timeLeftMs / 60000); const message = access.status === 'ip-banned' ? `由于安全策略,您的IP已被临时限制访问此功能,请在 ${minutes} 分钟后重试。` : `暗号尝试次数过多,该功能已锁定。请在 ${minutes} 分钟后重试。`; uiManager.showNotification('访问受限', message, 'error'); }; if (access.status === 'ip-banned' || access.status === 'locked') { showLockoutMessage(access.until); return; } const onValidationSuccess = () => { configManager.logAction('触发了神秘档案馆彩蛋', 'general'); window.electronAPI.resetSecretAttempts(); const currentTheme = document.body.getAttribute('data-theme') || 'dark'; uiManager.showActionableNotification( '暗号正确!', '一个通往过去的入口已为你敞开。', '进入档案馆', () => { window.electronAPI.openSecretWindow(currentTheme); } ); }; if (skipValidation) { onValidationSuccess(); return; } uiManager.showPrompt({ title: '秘密入口', label: '请输入暗号以继续…', placeholder: '注意大小写哦~', }, async (inputValue) => { if (inputValue && inputValue.toLowerCase() === 'vv') { onValidationSuccess(); } else if (inputValue !== null) { const result = await window.electronAPI.recordSecretFailure(); configManager.logAction(`彩蛋暗号尝试失败,剩余次数: ${result.attemptsLeft}`, 'general'); if (result.isLocked) { showLockoutMessage(result.lockoutUntil); } else { uiManager.showNotification('暗号错误', `访问被拒绝。你还有 ${result.attemptsLeft} 次机会。`, 'error'); } } }); } bindWindowControls() { document.getElementById('minimize-btn').addEventListener('click', () => window.electronAPI.minimizeWindow()); document.getElementById('maximize-btn').addEventListener('click', () => window.electronAPI.maximizeWindow()); document.getElementById('close-btn').addEventListener('click', () => window.electronAPI.closeWindow()); } bindNavigationEvents() { document.getElementById('home-btn').title = i18n.t('nav.home'); document.getElementById('toolbox-btn').title = i18n.t('nav.toolbox'); document.getElementById('log-btn').title = i18n.t('nav.logs'); document.getElementById('settings-btn').title = i18n.t('nav.settings'); const navActions = { 'home-btn': () => this.navigateTo('home').catch(error => { console.error('[MainPage] 导航到首页失败:', error); Application.handleError(error, 'MainPage.navActions.home'); }), 'toolbox-btn': () => this.navigateTo('toolbox').catch(error => { console.error('[MainPage] 导航到工具箱失败:', error); Application.handleError(error, 'MainPage.navActions.toolbox'); }), 'log-btn': () => this.navigateTo('logs').catch(error => { console.error('[MainPage] 导航到日志失败:', error); Application.handleError(error, 'MainPage.navActions.logs'); }), 'settings-btn': () => this.navigateTo('settings').catch(error => { console.error('[MainPage] 导航到设置失败:', error); Application.handleError(error, 'MainPage.navActions.settings'); }), }; for (const [id, action] of Object.entries(navActions)) { const btn = document.getElementById(id); if (btn) { btn.addEventListener('click', (e) => { try { action(); this.updateActiveNavButton(e.currentTarget); } catch (error) { console.error(`[MainPage] 导航按钮 ${id} 点击处理失败:`, error); Application.handleError(error, `MainPage.navActions.${id}`); } }); } else { console.warn(`[MainPage] 导航按钮 ${id} 不存在`); } } } /** * [修复] 导航方法 - 直接使用方法引用,避免 methodMap 属性被压缩 * 直接使用 this.methodName.bind(this),确保方法名在保留列表中 */ async navigateTo(page, subPage = null) { // [修复] 直接使用方法引用,避免 methodMap 属性被压缩导致的问题 // 这些方法名已在构建脚本的保留列表中,不会被压缩 const renderWelcomePage = this.renderWelcomePage?.bind(this); const renderToolHealthCheckPage = this.renderToolHealthCheckPage?.bind(this); const renderLogsPage = this.renderLogsPage?.bind(this); const renderSettingsPage = this.renderSettingsPage?.bind(this); const updateActiveNavButton = this.updateActiveNavButton?.bind(this); // [修复] 检查方法是否存在 if (!renderWelcomePage || !renderLogsPage || !renderSettingsPage) { console.error('[MainPage] 页面渲染方法未定义'); Application.handleError(new Error('页面渲染方法未定义'), 'MainPage.navigateTo'); return; } if (this.settingsInterval) { clearInterval(this.settingsInterval); this.settingsInterval = null; } if (this.settingsIntervalHeavy) { clearInterval(this.settingsIntervalHeavy); this.settingsIntervalHeavy = null; } if (this.dashboardInterval) { clearInterval(this.dashboardInterval); this.dashboardInterval = null; } if (this.activeChartInstance) { this.activeChartInstance.destroy(); this.activeChartInstance = null; } document.getElementById('update-slide-out-panel')?.remove(); // [修复] 关闭并移除公告模态框(如果打开的话) // [重构] 使用通用模态框框架关闭公告模态框 import('./ui/ModalManager.js').then(({ default: modalManager }) => { if (modalManager.isOpen('announcement-modal')) { modalManager.close('announcement-modal'); } }).catch(() => { // 如果 ModalManager 未加载,忽略 }); document.getElementById('update-panel-overlay')?.remove(); document.getElementById('dd-options-lang-select-wrapper')?.remove(); // [重构] 清理字体下载相关元素(使用通用模态框框架) const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options'; document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID_STR)?.remove(); // [重构] 使用通用模态框框架关闭字体下载模态框 import('./ui/ModalManager.js').then(({ default: modalManager }) => { if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) { modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID); } }).catch(() => { // 如果 ModalManager 未加载,忽略 }); uiManager.unloadActiveTool(); configManager.logAction(`导航到: ${page}`, 'navigation'); const contentArea = document.getElementById('content-area'); if (!contentArea) { console.error('[MainPage] content-area 元素不存在'); Application.handleError(new Error('content-area 元素不存在'), 'MainPage.navigateTo'); return; } if (contentArea.children.length > 0) { contentArea.classList.add('page-exit'); await new Promise(r => setTimeout(r, 150)); contentArea.classList.remove('page-exit'); } switch (page) { case 'home': if (renderWelcomePage) { await renderWelcomePage(); } else { console.error('[MainPage] renderWelcomePage 方法不存在'); } break; case 'toolbox': // 检查今天是否自检过 // 确保工具健康检查模块已加载 if (!window.toolHealthChecker) { // 如果模块未加载,等待加载完成(最多等待3秒) let retries = 0; const maxRetries = 30; // 30 * 100ms = 3秒 while (!window.toolHealthChecker && retries < maxRetries) { await new Promise(resolve => setTimeout(resolve, 100)); retries++; } } // 检查今天是否已经自检过 let needCheck = true; if (window.toolHealthChecker) { // 检查今天是否已经检查过(不修改日期) const today = new Date().toDateString(); const savedDate = configManager.config?.tool_health_last_check || null; if (savedDate === today) { needCheck = false; // 今天已经检查过 } } if (needCheck) { // 没有自检过,直接跳转到工具箱,健康检查在后台进行 // 确保模态框模块已加载 // [重构] 不再自动显示模态框,健康检查由工具箱验证页面处理 // 如果需要后台检查,可以直接调用 toolHealthChecker.checkAllTools(true, true) } // 直接跳转工具箱界面(会先显示验证界面) await uiManager.renderToolboxPage(); // 确保导航按钮状态正确更新 const toolboxBtn = document.getElementById('toolbox-btn'); if (toolboxBtn) { updateActiveNavButton(toolboxBtn); } break; case 'logs': if (renderLogsPage) { renderLogsPage(new Date().toISOString().split('T')[0]); } else { console.error('[MainPage] renderLogsPage 方法不存在'); } break; case 'settings': if (renderSettingsPage) { await renderSettingsPage(); } else { console.error('[MainPage] renderSettingsPage 方法不存在'); } if (subPage) { setTimeout(() => { const targetNavItem = document.querySelector(`.island-nav-item[data-panel="${subPage}"]`); if (targetNavItem) targetNavItem.click(); }, 100); } break; } contentArea.classList.add('page-enter'); setTimeout(() => { contentArea.classList.remove('page-enter'); }, 200); } updateActiveNavButton(activeButton) { if (!activeButton) return; document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active')); activeButton.classList.add('active'); const slider = document.getElementById('nav-active-slider'); if (slider) { const targetTop = activeButton.offsetTop; const targetHeight = activeButton.offsetHeight; slider.style.top = `${targetTop}px`; slider.style.height = `${targetHeight}px`; } } /** * [新增] 渲染工具健康检查页面(如果需要) * 这个方法用于在工具箱页面显示健康检查状态 */ renderToolHealthCheckPage() { // 这个方法实际上不需要单独渲染页面 // 健康检查在后台进行,工具箱页面正常显示 // 如果需要在页面中显示健康检查状态,可以在工具箱页面中添加 return Promise.resolve(); } async renderWelcomePage() { const toolStatus = configManager.config?.tool_status || {}; const searchToolStatus = toolStatus['smart-search'] || { enabled: true }; const isSmartSearchEnabled = searchToolStatus.enabled; const searchDisabledMessage = isSmartSearchEnabled ? "" : (searchToolStatus.message || i18n.t('home.search.disabled')); const contentArea = document.getElementById('content-area'); const appVersion = await window.electronAPI.getAppVersion(); // [修复] 获取公告内容,如果为空则不显示公告卡片 const fullAnnouncement = configManager.config?.home_notes || ''; const stripHtml = (html) => { if (!html) return ''; let tmp = document.createElement("DIV"); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ""; }; const plainText = stripHtml(fullAnnouncement); const hasAnnouncement = plainText && plainText.trim().length > 0; const truncatedText = plainText.length > 50 ? plainText.substring(0, 50) + '...' : plainText; const newNotes = configManager.config?.update_notes || {}; const getGreeting = () => { const hour = new Date().getHours(); if (hour < 6) return i18n.t('home.greeting.night'); if (hour < 12) return i18n.t('home.greeting.morning'); if (hour < 14) return i18n.t('home.greeting.noon'); if (hour < 18) return i18n.t('home.greeting.afternoon'); return i18n.t('home.greeting.evening'); }; const createNotesHtml = (notes) => { const entries = Object.entries(notes); if (entries.length === 0) return `

    ${i18n.t('home.updates.empty', null, '暂无更新详情。')}

    `; return entries.map(([key, value]) => `
    ${key}
    ${parseMarkdown(value)}
    `).join(''); }; contentArea.innerHTML = `

    ${getGreeting()}

    ${i18n.t('home.welcome')}

    ${hasAnnouncement ? `

    ${truncatedText}

    ${i18n.t('home.announcement.viewFull')}
    ` : ''}
    ${!isSmartSearchEnabled ? `

    ${i18n.t('tool.smartSearch.name')} 已禁用

    ${searchDisabledMessage}
    ` : ''}
    v${appVersion}
    `; // [修复] 根据是否有公告内容来决定淡入的元素 const fadeInSelectors = hasAnnouncement ? '.welcome-header, .compact-announcement, .welcome-search-bar' : '.welcome-header, .welcome-search-bar'; uiManager.fadeInContent(fadeInSelectors); // 更新日志面板 if (!document.getElementById('update-slide-out-panel')) { const panelHtml = `

    ${i18n.t('home.updates')}

    ${createNotesHtml(newNotes)}
    `; document.body.insertAdjacentHTML('beforeend', panelHtml); } // [修复] 公告弹窗(仅在需要时创建,并确保默认隐藏) // [重构] 公告模态框已由通用模态框框架动态创建,无需在HTML中预定义 // 遮罩层 if (!document.getElementById('update-panel-overlay')) { const overlayHtml = `
    `; document.body.insertAdjacentHTML('beforeend', overlayHtml); } if (this.dashboardInterval) { clearInterval(this.dashboardInterval); this.dashboardInterval = null; } const searchInput = document.getElementById('welcome-search-input'); const searchBtn = document.getElementById('welcome-search-btn'); const updatePanel = document.getElementById('update-slide-out-panel'); const showUpdateBtn = document.getElementById('show-update-log-btn'); const closeUpdateBtn = document.getElementById('close-update-panel-btn'); // [修复] 仅在存在公告内容时获取相关元素 const announcementCard = hasAnnouncement ? document.getElementById('compact-announcement-card') : null; // [重构] 公告模态框已由通用模态框框架动态创建,无需获取DOM元素 const updateOverlay = document.getElementById('update-panel-overlay'); if (isSmartSearchEnabled) { const onSearch = () => { const query = searchInput.value.trim(); if (query) { this._performWelcomeSearch(query); } else { uiManager.showNotification(i18n.t('common.notification.title.info'), i18n.t('home.search.placeholder'), 'info'); } }; searchBtn?.addEventListener('click', onSearch); searchInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') onSearch(); }); } const closeUpdatePanel = () => { updatePanel?.classList.remove('active'); updateOverlay?.classList.remove('active'); }; const openUpdatePanel = () => { updatePanel?.classList.add('active'); updateOverlay?.classList.add('active'); }; showUpdateBtn?.addEventListener('click', () => { if (updatePanel.classList.contains('active')) { closeUpdatePanel(); } else { openUpdatePanel(); } }); closeUpdateBtn?.addEventListener('click', closeUpdatePanel); updateOverlay?.addEventListener('click', closeUpdatePanel); // [重构] 绑定公告模态框事件(使用通用模态框框架) if (hasAnnouncement && announcementCard && fullAnnouncement && fullAnnouncement.trim()) { announcementCard.addEventListener('click', async () => { // 动态导入 ModalManager const { default: modalManager } = await import('./ui/ModalManager.js'); // 使用通用模态框框架创建公告模态框 modalManager.create({ id: 'announcement-modal', title: ` ${i18n.t('home.updates.modal.title', '完整公告')}`, content: `
    ${fullAnnouncement}
    `, size: 'medium', closable: true, closeOnBackdrop: true, className: 'announcement-modal-wrapper' }); }); } } async _performWelcomeSearch(query) { const resultsContainer = document.getElementById('welcome-search-results-container'); if (!resultsContainer) return; resultsContainer.innerHTML = `
    加载中...

    ${i18n.t('common.loading')}...

    `; const apiKey = configManager.config?.api_keys?.uapipro || null; const requestBody = { query: query, timeout_ms: 10000, fetch_full: false }; const requestHeaders = { 'Content-Type': 'application/json' }; if (apiKey) { requestHeaders['Authorization'] = `Bearer ${apiKey}`; } try { const response = await fetch('https://uapis.cn/api/v1/search/aggregate', { method: 'POST', headers: requestHeaders, body: JSON.stringify(requestBody) }); const data = await response.json(); if (!response.ok) throw new Error(data.message || `HTTP 错误 ${response.status}`); configManager.logAction(`[主页搜索] 成功: ${query}`, 'tool'); this._renderWelcomeResults(data, query); } catch (error) { configManager.logAction(`[主页搜索] 失败: ${error.message}`, 'error'); resultsContainer.innerHTML = `

    ${i18n.t('home.search.failed.title')}

    ${error.message}
    `; } } _renderWelcomeResults(data, query) { const resultsContainer = document.getElementById('welcome-search-results-container'); if (!resultsContainer) return; const results = data.results || []; if (results.length === 0) { resultsContainer.innerHTML = `

    ${i18n.t('home.search.results.empty.title', { query: query })}

    ${i18n.t('home.search.results.empty.sub')}
    `; return; } const formatTime = (isoString) => { if (!isoString) return ''; try { return new Date(isoString).toLocaleDateString(); } catch (e) { return ''; } }; const resultsHtml = results.map(item => `

    ${item.title}

    ${item.snippet}

    ${item.domain} ${item.author || '未知作者'} ${formatTime(item.publish_time) || '未知时间'} AI得分: ${item.score.toFixed(3)}
    `).join(''); const statsHtml = `
    ${i18n.t('home.search.results.stats', { count: data.total_results || 0, time: data.process_time_ms })}
    `; const detailsButtonHtml = ` `; resultsContainer.innerHTML = statsHtml + resultsHtml + detailsButtonHtml; resultsContainer.querySelectorAll('a[data-url]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); window.electronAPI.openExternalLink(e.currentTarget.dataset.url); }); }); resultsContainer.querySelector('#view-full-results-btn')?.addEventListener('click', () => { configManager.logAction(`[主页搜索] 跳转到工具: ${query}`, 'navigation'); // 1. 切换 Tab this.navigateTo('toolbox'); this.updateActiveNavButton(document.getElementById('toolbox-btn')); // 2. 启动工具 (同步操作,因为 launchModularTool 是同步替换 DOM) uiManager.launchModularTool('smart-search'); // 3. 延时填充数据,确保 DOM 渲染完成 setTimeout(() => { const toolInput = document.getElementById('ss-query-input'); const toolSearchBtn = document.getElementById('ss-search-btn'); if (toolInput && toolSearchBtn) { toolInput.value = query; toolSearchBtn.click(); // 自动触发搜索 } else { console.warn('[SmartSearch] 自动填充失败:未找到工具 DOM 元素'); } }, 300); // 增加延时到 300ms }); } renderLogsPage(filterDate = new Date().toISOString().split('T')[0]) { const contentArea = document.getElementById('content-area'); const todayString = new Date().toISOString().split('T')[0]; contentArea.innerHTML = `
    运行日志
    加载中...

    ${i18n.t('common.loading')}...

    `; this.bindLogPageEvents(); this._fetchAndRenderLogs(filterDate); } async _fetchAndRenderLogs(filterDate) { const dateQuery = filterDate === 'all' ? null : filterDate; let logs; const logsContainer = document.getElementById('logs-content-container'); try { logs = await window.electronAPI.getLogs(dateQuery); } catch (error) { if (logsContainer) { logsContainer.innerHTML = `

    日志加载失败

    ${error.message}
    `; } return; } const groupedLogs = logs.reduce((acc, log) => { const date = new Date(log.timestamp).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-'); if (!acc[date]) acc[date] = []; acc[date].push(log); return acc; }, {}); const renderLogEntries = (grouped) => { if (Object.keys(grouped).length === 0) { return `

    暂无日志记录

    系统运行平稳,无事发生。
    `; } let html = ''; const categoryMap = { system: { icon: 'fa-server', class: 'log-tag-system' }, navigation: { icon: 'fa-location-arrow',class: 'log-tag-nav' }, ui: { icon: 'fa-swatchbook', class: 'log-tag-ui' }, tool: { icon: 'fa-microchip', class: 'log-tag-tool' }, update: { icon: 'fa-rocket', class: 'log-tag-update' }, settings: { icon: 'fa-sliders-h', class: 'log-tag-settings' }, error: { icon: 'fa-bomb', class: 'log-tag-error' }, general: { icon: 'fa-info-circle', class: 'log-tag-general' } }; for (const date in grouped) { const dayOfWeek = new Date(grouped[date][0].timestamp).toLocaleDateString('zh-CN', { weekday: 'long' }); html += `
    `; html += `
    ${date} · ${dayOfWeek}
    `; html += grouped[date].map(log => { const catInfo = categoryMap[log.category] || categoryMap.general; const time = new Date(log.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); let mainAction = log.action; let subTagHtml = ''; const subCategoryMatch = log.action.match(/^\[(.*?)\]\s(.*)$/); if (subCategoryMatch) { const subCategoryName = subCategoryMatch[1]; mainAction = subCategoryMatch[2]; subTagHtml = `${subCategoryName}`; } // 使用 data 属性存储要复制的内容,避免内联 onclick 被混淆器破坏 const logId = `log-${log.timestamp}-${Math.random().toString(36).substr(2, 9)}`; return `
    ${time}
    ${subTagHtml}${mainAction}
    `; }).join(''); html += `
    `; } return html; }; if (logsContainer) { logsContainer.innerHTML = renderLogEntries(groupedLogs); const cards = logsContainer.querySelectorAll('.log-card-island'); cards.forEach((card, i) => { card.style.opacity = '0'; card.style.transform = 'translateY(10px)'; card.style.animation = `contentFadeIn 0.4s ease forwards ${i * 0.03}s`; }); // 绑定日志复制按钮事件(避免内联 onclick 被混淆器破坏) logsContainer.querySelectorAll('.log-copy-btn').forEach(btn => { btn.addEventListener('click', async () => { const copyText = btn.getAttribute('data-copy-text'); if (copyText) { try { await navigator.clipboard.writeText(copyText); // 可以添加一个短暂的视觉反馈 const icon = btn.querySelector('i'); if (icon) { const originalClass = icon.className; icon.className = 'fas fa-check'; setTimeout(() => { icon.className = originalClass; }, 1000); } } catch (err) { console.error('复制失败:', err); } } }); }); } } bindLogPageEvents() { const datePicker = document.getElementById('log-date-picker'); const logsContainer = document.getElementById('logs-content-container'); const loadLogs = (filterDate) => { if (logsContainer) { logsContainer.innerHTML = `
    加载中...

    ${i18n.t('common.loading')}...

    `; } this._fetchAndRenderLogs(filterDate); }; datePicker.addEventListener('change', (e) => { loadLogs(e.target.value); }); document.getElementById('show-today-logs').addEventListener('click', () => { const todayString = new Date().toISOString().split('T')[0]; datePicker.value = todayString; loadLogs(todayString); }); document.getElementById('show-all-logs').addEventListener('click', () => { loadLogs('all'); }); document.getElementById('clear-logs')?.addEventListener('click', async () => { if (confirm('确定要清空所有日志记录吗?此操作不可逆!')) { await window.electronAPI.clearLogs(); uiManager.showNotification(i18n.t('common.notification.title.success'), '所有日志已被清空。'); loadLogs(datePicker.value || 'all'); } }); } async renderSettingsPage() { const contentArea = document.getElementById('content-area'); const [appVersion, traffic, langConfig] = await Promise.all([ window.electronAPI.getAppVersion(), window.electronAPI.getTrafficStats(), window.electronAPI.getLanguageConfig() ]); const configVersion = configManager.config?.config_version || 'N/A'; const langOptions = [ { key: 'auto', label: i18n.t('settings.appearance.language.auto') }, { key: 'zh-CN', label: i18n.t('settings.appearance.language.zh-CN') }, { key: 'en-US', label: i18n.t('settings.appearance.language.en-US') } ]; const langSelectHtml = `
    ${langOptions.find(o => o.key === langConfig.current)?.label || 'N/A'}
    `; const langOptionsHtml = `
    ${langOptions.map(opt => `
    ${opt.label}
    `).join('')}
    `; const availableFonts = [ { name: 'Default', label: '系统默认 (Default)', url: '' }, { name: 'MeiGanShouXieTi', label: '梅干手写体', url: 'https://update.ymhut.cn/fonts/MeiGanShouXieTi-2.ttf' }, { name: 'QianTuBiFeng', label: '千图笔锋手写体', url: 'https://update.ymhut.cn/fonts/QianTuBiFengShouXieTi-2.ttf' }, { name: 'YOzBS', label: 'YOz手写体', url: 'https://update.ymhut.cn/fonts/YOzBS-2.otf' } ]; const currentFontName = this.dbSettings.customFontName || 'Default'; // [重构] 字体下载相关常量定义(统一管理,避免冲突) // 注意:APPEARANCE_FONT_MODAL_ID 已在类级别定义为静态常量 const APPEARANCE_FONT_STATUS_ID = 'appearance-font-download-status'; const APPEARANCE_FONT_PROGRESS_BAR_ID = 'appearance-font-download-bar'; const APPEARANCE_FONT_PERCENT_ID = 'appearance-font-download-percent'; const APPEARANCE_FONT_SELECT_WRAPPER_ID = 'appearance-font-select-wrapper'; const APPEARANCE_FONT_SELECT_OPTIONS_ID = 'appearance-font-select-options'; const APPEARANCE_FONT_SELECT_VALUE_ID = 'appearance-font-select-value'; const APPEARANCE_DEFAULT_FONT_VALUE = 'Default'; const APPEARANCE_FONT_CONFIG_TYPE = 'appearance_config_font'; const fontSelectHtml = `
    ${availableFonts.find(f => f.name === currentFontName)?.label || '系统默认'}
    `; const fontOptionsHtml = `
    ${availableFonts.map(f => `
    ${f.label}
    `).join('')}
    `; // [重构] 字体下载模态框已由通用模态框框架动态创建,无需在HTML中预定义 contentArea.innerHTML = `

    ${i18n.t('settings.appearance.title', null, '个性化')}

    ${i18n.t('settings.appearance.language', null, '界面语言')}${langSelectHtml}
    ${i18n.t('settings.appearance.font', null, 'UI 字体')}${fontSelectHtml}

    ${i18n.t('settings.traffic.title', null, '网络监控')}

    ${i18n.t('settings.traffic.total', null, '累计消耗')} ${configManager.formatBytes(traffic)}

    ${i18n.t('settings.toolHealth.title', '工具健康检查')}

    ${i18n.t('settings.toolHealth.description', '自动检测网络工具的健康状态')}

    自动检查机制 系统会自动检查使用API和网络工具的状态,如果工具无法正常访问(404、502、503等错误),将自动锁定该工具。每天只检查一次,您也可以手动触发检查。
    ${i18n.t('settings.toolHealth.lockedTools', '已锁定工具')}
    ${i18n.t('settings.toolHealth.noLocked', '暂无锁定工具')}
    ${i18n.t('settings.toolHealth.lastCheck', '上次检查时间')}
    ${i18n.t('settings.toolHealth.never', '从未检查')}
    ${i18n.t('settings.toolHealth.networkTools', '网络工具总数')}
    ${i18n.t('settings.toolHealth.calculating', '计算中...')}
    Avatar

    YMHUT

    ${i18n.t('settings.about.motto', null, '功不唐捐,玉汝于成')}

    ${i18n.t('settings.about.title', null, '关于与软件环境')}

    ${i18n.t('settings.about.version', null, '客户端版本')}v${appVersion}
    ${i18n.t('settings.about.configVersion', null, '配置版本')}${configVersion}
    ${langOptionsHtml} ${fontOptionsHtml} `; const langOptionsEl = document.getElementById('dd-options-lang-select-wrapper'); if (langOptionsEl) document.body.appendChild(langOptionsEl); // [修复] 使用已在方法开头定义的常量(不重复声明) const fontOptionsEl = document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID); if (fontOptionsEl) document.body.appendChild(fontOptionsEl); // [重构] 字体下载模态框已由通用模态框框架动态创建,无需在HTML中预定义 // 字体下载模态框将在用户选择字体时动态创建 this.bindSettingsPageEvents(); // [修复] 延时渲染,确保 DOM 插入完成 setTimeout(() => { this.renderTrafficChart(); this.renderUpdateUI('idle', { currentVersion: appVersion }); }, 150); } // --- [核心修改区] 更新管理逻辑重构 --- // 1. 检查更新入口 async checkForUpdates(manual = false) { if (this.isDownloadingUpdate) return; // 如果是手动检查,显示 Loading 状态 if (manual) { this.renderUpdateUI('checking'); } try { const result = await window.electronAPI.checkUpdates(); // 模拟一点延时让动画不至于一闪而过 setTimeout(() => { if (result.hasUpdate) { this.renderUpdateUI('available', result); } else { if (manual) { this.renderUpdateUI('latest', { currentVersion: result.currentVersion }); } } }, 800); } catch (error) { if (manual) { uiManager.showNotification('检查失败', error.message, 'error'); // 恢复到当前版本显示 const ver = configManager.config.app_version || 'Unknown'; this.renderUpdateUI('idle', { currentVersion: ver }); } } } // 2. 渲染更新 UI (多状态机) renderUpdateUI(state, data = {}) { const container = document.getElementById('update-state-container'); if (!container) return; let html = ''; const containerClass = `update-status-container update-state-${state}`; switch (state) { case 'idle': html = `
    更新管理
    当前版本: v${data.currentVersion}
    点击下方按钮检查是否有新版本发布。
    `; break; case 'checking': html = `
    正在检查...
    正在连接服务器获取最新版本信息,请稍候。
    `; break; case 'latest': html = `
    已是最新版本
    您当前运行的是 v${data.currentVersion}。
    系统运行在最佳状态。
    `; break; case 'available': // [新增] 多线路下载 UI 渲染逻辑 const notes = Object.entries(data.updateNotes || {}).map(([k, v]) => `${k}
    ${parseMarkdown(v)}`).join('

    '); // 构建镜像列表 HTML let mirrorsHtml = ''; const mirrors = data.downloadMirrors || []; // 如果有配置镜像列表,显示选择器 if (mirrors.length > 0) { mirrorsHtml = `

    选择下载线路:

    ${mirrors.filter(m => m.enabled !== false).map(m => { const icon = m.type === 'web_redirect' ? 'fa-external-link-alt' : 'fa-cloud-download-alt'; const tagClass = m.type === 'web_redirect' ? 'web' : 'direct'; const tagName = m.type === 'web_redirect' ? '网页' : '直连'; return ` `; }).join('')}
    `; } else { // 兼容旧逻辑:默认按钮 mirrorsHtml = `
    `; } html = `
    发现新版本 v${data.remoteVersion}
    ${notes || '暂无更新日志'}
    ${mirrorsHtml}
    `; break; case 'downloading': html = `
    正在下载 v${data.remoteVersion} 0%
    0 KB/s 初始化连接...
    `; break; } container.innerHTML = html; // 重新绑定事件 if (state === 'idle' || state === 'latest') { const btn = document.getElementById(state === 'idle' ? 'update-check-btn' : 'update-recheck-btn'); btn?.addEventListener('click', () => this.checkForUpdates(true)); } else if (state === 'available') { // 绑定镜像按钮点击 const mirrorBtns = container.querySelectorAll('.mirror-btn'); if (mirrorBtns.length > 0) { mirrorBtns.forEach(btn => { btn.addEventListener('click', () => { this._handleDownloadAction(btn.dataset.url, btn.dataset.type, data); }); }); } else { // 兼容旧按钮 document.getElementById('update-dl-pkg-btn')?.addEventListener('click', (e) => this.startUpdateDownload(e.target.dataset.url, data, false)); document.getElementById('update-install-btn')?.addEventListener('click', (e) => this.startUpdateDownload(e.target.dataset.url, data, true)); } } else if (state === 'downloading') { document.getElementById('update-cancel-btn')?.addEventListener('click', () => window.electronAPI.cancelDownload()); } } // 3. 处理下载动作分流 _handleDownloadAction(url, type, updateInfo) { if (type === 'web_redirect') { uiManager.showNotification('提示', '正在打开下载页面,请在弹出的窗口中手动完成下载。', 'info'); if (window.electronAPI.openDownloadWindow) { window.electronAPI.openDownloadWindow(url); } else { window.electronAPI.openExternalLink(url); } } else { // 直连下载,默认下载后询问安装 this.startUpdateDownload(url, updateInfo, true); } } // 4. 开始直连下载 async startUpdateDownload(url, updateInfo, installAfter) { if (this.isDownloadingUpdate) return; this.isDownloadingUpdate = true; this.renderUpdateUI('downloading', updateInfo); try { const result = await window.electronAPI.downloadUpdate(url); if (result.success) { if (installAfter) { document.getElementById('dl-status-text').textContent = '下载完成,正在启动安装...'; setTimeout(() => window.electronAPI.installUpdate(result.path), 1000); } else { window.electronAPI.showItemInFolder(result.path); this.renderUpdateUI('idle', { currentVersion: updateInfo.currentVersion }); } } } catch (error) { uiManager.showNotification('下载失败', error.error, 'error'); this.renderUpdateUI('available', updateInfo); } finally { this.isDownloadingUpdate = false; } } // 5. 监听进度 (逻辑保持,适配新UI ID) listenForDownloadProgress() { window.electronAPI.onDownloadProgress(p => { const bar = document.getElementById('dl-progress-bar'); const percent = document.getElementById('dl-percent-text'); const speed = document.getElementById('dl-speed-text'); if (bar && percent) { bar.style.width = `${p.percent}%`; percent.textContent = `${Math.round(p.percent)}%`; if(speed) speed.textContent = configManager.formatBytes(p.speed || 0) + '/s'; } }); } listenForGlobalNetworkSpeed() { const netSpeedEl = document.getElementById('network-speed'); window.electronAPI.onNetworkSpeedUpdate(({ speed }) => { netSpeedEl.innerHTML = ` ${configManager.formatBytes(speed)}/s`; }); } // --- [修改结束] --- navigateToSettingsForUpdate() { this.navigateTo('settings', 'update'); this.updateActiveNavButton(document.getElementById('settings-btn')); setTimeout(() => { // 如果已经在 update 面板且是 idle 状态,自动点检查 const container = document.getElementById('update-state-container'); if (container && container.querySelector('.update-state-idle')) { const btn = document.getElementById('update-check-btn'); if(btn) btn.click(); } }, 500); } addRippleEffectListener() { document.addEventListener('click', function (e) { const target = e.target.closest('.ripple'); if (target) { const rect = target.getBoundingClientRect(); const ripple = document.createElement('span'); const diameter = Math.max(target.clientWidth, target.clientHeight); const radius = diameter / 2; ripple.style.width = ripple.style.height = `${diameter}px`; ripple.style.left = `${e.clientX - rect.left - radius}px`; ripple.style.top = `${e.clientY - rect.top - radius}px`; ripple.classList.add('ripple-effect'); const existingRipple = target.querySelector('.ripple-effect'); if (existingRipple) existingRipple.remove(); target.appendChild(ripple); setTimeout(() => ripple.remove(), 600); } }); } async renderTrafficChart() { if (this.activeChartInstance) { this.activeChartInstance.destroy(); this.activeChartInstance = null; } const history = await window.electronAPI.getTrafficHistory(); const labels = [], dataPoints = [], today = new Date(); for (let i = 13; i >= 0; i--) { const d = new Date(); d.setDate(today.getDate() - i); const dateStr = d.toISOString().split('T')[0]; const record = history ? history.find(h => h.log_date === dateStr) : null; labels.push(dateStr.substring(5)); dataPoints.push(((record ? record.bytes_used : 0) / (1024 * 1024)).toFixed(2)); } const ctx = document.getElementById('traffic-chart')?.getContext('2d'); if (!ctx) return; const style = getComputedStyle(document.documentElement); const currentFontFamily = style.getPropertyValue('--font-family').trim() || "sans-serif"; const primaryColorRGB = style.getPropertyValue('--primary-rgb').trim(); const textColor = style.getPropertyValue('--text-color').trim(); const borderColor = style.getPropertyValue('--border-color').trim(); const gradient = ctx.createLinearGradient(0, 0, 0, 250); gradient.addColorStop(0, `rgba(${primaryColorRGB}, 0.5)`); gradient.addColorStop(1, `rgba(${primaryColorRGB}, 0.05)`); this.activeChartInstance = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: '每日流量 (MB)', data: dataPoints, borderColor: `rgba(${primaryColorRGB}, 1)`, backgroundColor: gradient, borderWidth: 2, fill: true, tension: 0.4, pointBackgroundColor: `rgba(${primaryColorRGB}, 1)`, pointRadius: 3, pointHoverRadius: 6 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(0,0,0,0.8)', titleColor: '#fff', titleFont: { family: currentFontFamily, weight: 'bold' }, bodyFont: { family: currentFontFamily }, callbacks: { label: (ctx) => ` ${ctx.parsed.y} MB` } } }, scales: { y: { beginAtZero: true, grid: { color: borderColor, borderDash: [5, 5] }, ticks: { color: textColor, font: { size: 10, family: currentFontFamily } }, title: { display: true, text: '每日使用量 (MB)', color: textColor, font: { family: currentFontFamily } } }, x: { grid: { display: false }, ticks: { color: textColor, font: { size: 10, family: currentFontFamily } } } }, interaction: { mode: 'nearest', axis: 'x', intersect: false } } }); } toggleOpacityControls(enabled) {} async toggleTheme(event = null) { // [修复] 确保字体下载模态框在主题切换开始时关闭 // [重构] 使用通用模态框框架关闭字体下载模态框 try { const { default: modalManager } = await import('./ui/ModalManager.js'); if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) { modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID); } } catch (e) { // ModalManager 未加载时忽略(不应该发生) console.warn('[MainPage] ModalManager 未加载,无法关闭字体下载模态框'); } const currentTheme = document.body.getAttribute('data-theme') || 'dark'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; // [日志] 记录主题切换 if (configManager && configManager.logAction) { configManager.logAction(`切换主题: ${currentTheme} -> ${newTheme}`, 'ui'); } // [重构] 使用新的月亮/太阳过渡动画 await this._applyThemeTransitionWithAnimation(newTheme, event); } /** * [重构] 应用主题过渡动画(带月亮/太阳动画) * 创建流畅的月亮/太阳滑入和发光动画 * @param {string} newTheme - 新主题名称 ('dark' | 'light') * @param {Event} event - 触发事件(用于获取点击位置) */ async _applyThemeTransitionWithAnimation(newTheme, event = null) { // [修复] 始终使用屏幕中心位置,确保月亮/太阳在软件正中间 const triggerX = window.innerWidth / 2; const triggerY = window.innerHeight / 2; // 创建月亮/太阳动画覆盖层(固定在屏幕中心) this._createThemeCelestialAnimation(triggerX, triggerY, newTheme); // 等待动画开始后应用主题 await new Promise(resolve => setTimeout(resolve, 200)); // 应用主题过渡 await this._applyThemeTransition(newTheme); } /** * [新增] 创建月亮/太阳主题切换动画 * @param {number} x - 触发X坐标 * @param {number} y - 触发Y坐标 * @param {string} newTheme - 新主题 */ _createThemeCelestialAnimation(x, y, newTheme) { // 移除旧的动画元素 const oldAnimation = document.getElementById('theme-celestial-animation'); if (oldAnimation) oldAnimation.remove(); // 创建动画容器 const animation = document.createElement('div'); animation.id = 'theme-celestial-animation'; animation.className = 'theme-celestial-animation'; animation.setAttribute('data-theme', newTheme); // [修复] 不再使用触发位置,固定使用屏幕中心(CSS中已设置为50%) // 注意:x和y参数保留用于未来可能的扩展,但实际位置由CSS固定为屏幕中心 // 创建月亮或太阳图标 const isDark = newTheme === 'dark'; const iconSize = 120; const iconHtml = isDark ? `
    ` : `
    `; animation.innerHTML = iconHtml; document.body.appendChild(animation); // 触发动画 requestAnimationFrame(() => { animation.classList.add('active'); }); // 动画结束后移除元素 setTimeout(() => { animation.classList.add('fade-out'); setTimeout(() => { if (animation.parentNode) { animation.remove(); } }, 600); }, 1200); } /** * [重构] 应用主题过渡动画 * 解决文字显示问题,使用更平滑的过渡 * 与月亮/太阳动画配合使用 * @param {string} newTheme - 新主题名称 ('dark' | 'light') */ async _applyThemeTransition(newTheme) { // [重构] 检查是否有打开的模态框,需要特殊处理(使用通用模态框框架) let isModalOpen = false; let modalManager = null; try { const { default: mgr } = await import('./ui/ModalManager.js'); modalManager = mgr; isModalOpen = modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID); } catch (e) { // ModalManager 未加载时忽略(不应该发生) console.warn('[MainPage] ModalManager 未加载'); } // [重构] 确保字体下载模态框在主题切换时不会意外显示(已由通用模态框框架处理) // 添加过渡类,确保所有元素都有过渡效果 document.documentElement.classList.add('theme-transitioning'); // 等待月亮/太阳动画开始(200ms后) await new Promise(resolve => setTimeout(resolve, 200)); // 使用 requestAnimationFrame 确保在下一帧应用主题 await new Promise(resolve => requestAnimationFrame(resolve)); // 应用新主题 document.body.setAttribute('data-theme', newTheme); this.dbSettings.theme = newTheme; // [修复] 如果模态框打开,强制更新其样式 if (isModalOpen && modalManager) { const modal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID); if (modal) { const modalCard = modal.querySelector('.universal-modal-container'); if (modalCard) { // 强制重排模态框,确保样式更新 modalCard.offsetHeight; } } } // 更新主题切换按钮状态 const themeSwitchInput = document.getElementById('theme-switch-input'); if (themeSwitchInput) { themeSwitchInput.checked = newTheme === 'dark'; } // [降级] 如果使用旧的图标方式,更新图标 const themeIcon = document.querySelector('#theme-toggle i'); if (themeIcon) { themeIcon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } // 保存到数据库 window.electronAPI.setTheme(newTheme); // 强制重绘,确保文字正确显示 // 使用双重 requestAnimationFrame 确保浏览器完成重排和重绘 await new Promise(resolve => { requestAnimationFrame(() => { // 强制同步布局,确保所有样式已应用 document.body.offsetHeight; // [修复] 如果模态框打开,再次强制更新 // [重构] 重新获取模态框状态(使用通用模态框框架) if (isModalOpen && modalManager) { const currentFontModal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID); if (currentFontModal) { const modalCard = currentFontModal.querySelector('.universal-modal-container'); if (modalCard) { modalCard.offsetHeight; } } } requestAnimationFrame(() => { // 再次强制布局 document.body.offsetHeight; // [修复] 如果模态框打开,确保其样式已更新 if (isModalOpen && modalManager) { const modal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID); if (modal) { const modalCard = modal.querySelector('.universal-modal-container'); if (modalCard) { modalCard.offsetHeight; } } } // 更新图表(如果需要) if (document.getElementById('traffic-chart')) { this.renderTrafficChart(); } // 再次等待一帧,确保文字渲染完成 requestAnimationFrame(() => { resolve(); }); }); }); }); // 等待过渡完成后再移除过渡类(与动画时长同步) setTimeout(() => { document.documentElement.classList.remove('theme-transitioning'); }, 1200); } /** * [已废弃] 创建主题切换的水面波纹扩散动画 * 已替换为新的月亮/太阳动画系统 */ _createThemeRippleAnimation(x, y, newTheme) { // 此方法已不再使用,保留用于向后兼容 // 现在使用 _createThemeCelestialAnimation 代替 } bindThemeToggle() { const themeToggle = document.getElementById('theme-toggle'); if (themeToggle) { // [恢复] 使用之前的简单图标按钮样式 const icon = themeToggle.querySelector('i'); if (icon) { const currentTheme = document.body.getAttribute('data-theme') || 'dark'; icon.className = currentTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } themeToggle.addEventListener('click', async (e) => { // [修复] 确保字体下载模态框在主题切换时保持关闭 // [重构] 使用通用模态框框架关闭字体下载模态框 try { const { default: modalManager } = await import('./ui/ModalManager.js'); if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) { modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID); } } catch (e) { // ModalManager 未加载时忽略(不应该发生) console.warn('[MainPage] ModalManager 未加载,无法关闭字体下载模态框'); } // [修复] 检查当前是否在设置-外观界面,如果是则额外保护 const appearancePanel = document.getElementById('appearance-panel'); if (appearancePanel && appearancePanel.classList.contains('active')) { // [重构] 在设置-外观界面,确保字体选择下拉框不会触发(使用常量字符串) const APPEARANCE_FONT_SELECT_WRAPPER_ID_STR = 'appearance-font-select-wrapper'; const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options'; const fontSelectWrapper = document.getElementById(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR); if (fontSelectWrapper) { const fontOptions = document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID_STR); if (fontOptions && fontOptions.classList.contains('dynamic-active')) { fontOptions.classList.remove('dynamic-active'); } } } this.toggleTheme(e); }); } } bindSettingsPageEvents() { if (this.settingsInterval) clearInterval(this.settingsInterval); if (this.settingsIntervalHeavy) clearInterval(this.settingsIntervalHeavy); const updateLightStats = async () => { try { const [stats, mem] = await Promise.all([ window.electronAPI.getRealtimeStats(), window.electronAPI.getMemoryUpdate() ]); const cpuText = document.getElementById('cpu-usage-text'); if (cpuText) cpuText.textContent = `${stats.cpu}%`; const cpuBar = document.getElementById('cpu-usage-bar'); if (cpuBar) cpuBar.style.width = `${stats.cpu}%`; const memText = document.getElementById('mem-usage-text'); if (memText) memText.textContent = `${mem.usagePercentage}%`; const memBar = document.getElementById('mem-usage-bar'); if (memBar) memBar.style.width = `${mem.usagePercentage}%`; const uptimeText = document.getElementById('app-uptime-text'); if (uptimeText) uptimeText.textContent = stats.uptime; } catch (error) { if (this.settingsInterval) clearInterval(this.settingsInterval); } }; this.settingsInterval = setInterval(updateLightStats, 2000); updateLightStats(); const navItems = document.querySelectorAll('.island-nav-item'); navItems.forEach(item => { item.addEventListener('click', () => { const targetId = item.dataset.panel + '-panel'; const targetPanel = document.getElementById(targetId); const currentPanel = document.querySelector('.settings-panel.active'); if (currentPanel === targetPanel) return; navItems.forEach(n => n.classList.remove('active')); item.classList.add('active'); if (currentPanel) { currentPanel.classList.add('panel-exit'); currentPanel.classList.remove('active'); setTimeout(() => { currentPanel.classList.remove('panel-exit'); }, 300); } if (targetPanel) { void targetPanel.offsetWidth; targetPanel.classList.add('active'); } }); }); uiManager.setupAdaptiveDropdown('dd-wrapper-lang-select-wrapper', 'dd-options-lang-select-wrapper', async (value) => { await this.handleLanguageChange(value); } ); // [修复] 使用字符串常量(避免重复声明,bindSettingsPageEvents 是独立方法) // 注意:APPEARANCE_FONT_MODAL_ID 使用类级别静态常量 MainPage.APPEARANCE_FONT_MODAL_ID const APPEARANCE_FONT_SELECT_WRAPPER_ID_STR = 'appearance-font-select-wrapper'; const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options'; const APPEARANCE_FONT_STATUS_ID_STR = 'appearance-font-download-status'; const APPEARANCE_FONT_PROGRESS_BAR_ID_STR = 'appearance-font-download-bar'; const APPEARANCE_FONT_PERCENT_ID_STR = 'appearance-font-download-percent'; const APPEARANCE_DEFAULT_FONT_VALUE_STR = 'Default'; const APPEARANCE_FONT_CONFIG_TYPE_STR = 'appearance_config_font'; uiManager.setupAdaptiveDropdown(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR, APPEARANCE_FONT_SELECT_OPTIONS_ID_STR, async (value, text, dataset, triggerElement) => { // [重构] 防止主题切换时误触发字体下载 // 验证触发来源:必须是字体选择下拉框的选项被点击 if (!value || !text) { console.warn('[AppearanceFontDownload] 无效的字体选择参数,忽略触发'); return; } // [重构] 验证 dataset 是否包含有效的字体信息(除非是默认字体) if (value !== APPEARANCE_DEFAULT_FONT_VALUE_STR && (!dataset || !dataset.url)) { console.warn('[AppearanceFontDownload] 无效的字体数据,忽略触发'); return; } // [重构] 处理默认字体(在创建模态框之前) if (value === APPEARANCE_DEFAULT_FONT_VALUE_STR) { // [日志] 记录恢复默认字体 if (configManager && configManager.logAction) { configManager.logAction('恢复系统默认字体', 'ui'); } this.dbSettings.customFontName = ''; this.dbSettings.customFontPath = ''; configManager.applyAppSettings({ customFontFamily: false }); await window.electronAPI.saveMedia({ type: APPEARANCE_FONT_CONFIG_TYPE_STR, name: '', path: '' }); uiManager.showNotification('设置成功', '已恢复系统默认字体'); triggerElement.innerHTML = `系统默认 (${APPEARANCE_DEFAULT_FONT_VALUE_STR})`; return; } const fontUrl = dataset?.url || ''; // [重构] 参数验证 if (!fontUrl) { uiManager.showNotification('错误', '字体下载地址无效', 'error'); return; } // [重构] 双重验证:确保不是主题切换触发的 // 检查当前是否有主题切换正在进行 if (document.documentElement.classList.contains('theme-transitioning')) { console.warn('[AppearanceFontDownload] 主题切换正在进行,忽略字体下载请求'); return; } // [重构] 检查当前是否在设置-外观界面 const appearancePanel = document.getElementById('appearance-panel'); if (appearancePanel && appearancePanel.classList.contains('active')) { // 在设置-外观界面,额外检查是否真的是字体选择操作 const fontSelectWrapper = document.getElementById(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR); if (!fontSelectWrapper || !fontSelectWrapper.contains(document.activeElement)) { console.warn('[AppearanceFontDownload] 在设置-外观界面,但字体选择下拉框未激活,忽略触发'); return; } } // [日志] 记录字体下载开始 if (configManager && configManager.logAction) { configManager.logAction(`开始下载字体: ${value} (${text})`, 'ui'); } // [完善] 更新UI状态 triggerElement.innerHTML = `${text}`; // [重构] 使用通用模态框框架创建字体下载模态框 const { default: modalManager } = await import('./ui/ModalManager.js'); // 创建字体下载模态框 const fontModalContent = `

    正在配置字体

    准备下载...

    0%

    `; const modal = modalManager.create({ id: MainPage.APPEARANCE_FONT_MODAL_ID, title: '字体下载', content: fontModalContent, size: 'small', closable: false, // 下载过程中不允许关闭 closeOnBackdrop: false, className: 'font-download-modal-wrapper' }); // 获取模态框内的元素 const statusText = document.getElementById('appearance-font-download-status'); const progressBar = document.getElementById('appearance-font-download-bar'); const percentText = document.getElementById('appearance-font-download-percent'); // [重构] 参数验证 if (!modal || !statusText || !progressBar || !percentText) { console.error('[AppearanceFontDownload] 模态框元素未找到'); uiManager.showNotification('错误', '字体下载界面初始化失败', 'error'); return; } // 更新初始状态 statusText.textContent = `正在准备下载 ${value}...`; progressBar.style.width = '0%'; percentText.textContent = '0%'; // [完善] 延迟执行下载,确保UI已更新 setTimeout(async () => { // [重构] 注册进度回调(使用命名函数,便于移除) const progressCallback = (data) => { if (!data || typeof data.percent !== 'number') { console.warn('[AppearanceFontDownload] 无效的进度数据:', data); return; } const p = Math.round(data.percent); progressBar.style.width = `${p}%`; percentText.textContent = `${p}%`; if (data.received !== undefined && data.total !== undefined) { statusText.textContent = `下载中... ${configManager.formatBytes(data.received)} / ${configManager.formatBytes(data.total)}`; } else { statusText.textContent = `下载中... ${p}%`; } }; window.electronAPI.onFontDownloadProgress(progressCallback); try { // [重构] 使用明确的参数名称 const downloadParams = { fontName: value, fontUrl: fontUrl }; const result = await window.electronAPI.downloadFont(downloadParams); if (result && result.success) { progressBar.style.width = '100%'; percentText.textContent = '100%'; statusText.textContent = result.cached ? '字体已存在,验证通过' : '下载完成,正在应用...'; // [日志] 记录字体下载成功 if (configManager && configManager.logAction) { configManager.logAction( `字体下载成功: ${value}${result.cached ? ' (已缓存)' : ''}`, 'ui' ); } // [重构] 保存字体配置(使用新的常量) this.dbSettings.customFontName = value; this.dbSettings.customFontPath = result.path || ''; await window.electronAPI.saveMedia({ type: APPEARANCE_FONT_CONFIG_TYPE_STR, name: value, path: result.path || '' }); setTimeout(async () => { // [重构] 使用通用模态框框架关闭模态框 const { default: modalManager } = await import('./ui/ModalManager.js'); modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID); uiManager.showNotification('设置成功', '即将重启软件以应用字体...'); setTimeout(() => { window.electronAPI.relaunchApp(); }, 1000); }, 800); } else { throw new Error(result?.error || '下载失败:未知错误'); } } catch (err) { console.error('[AppearanceFontDownload] 下载失败:', err); // [日志] 记录字体下载失败 if (configManager && configManager.logAction) { configManager.logAction(`字体下载失败: ${value} - ${err.message}`, 'error'); } // [重构] 使用通用模态框框架关闭模态框 const { default: modalManager } = await import('./ui/ModalManager.js'); modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID); uiManager.showNotification('下载失败', err.message || '未知错误', 'error'); } }, 500); } ); const aboutNavButton = document.querySelector('.island-nav-item[data-panel="about"]'); if (aboutNavButton) aboutNavButton.addEventListener('contextmenu', (e) => { e.preventDefault(); this.handleSecretTrigger(false); }); document.getElementById('more-info-btn')?.addEventListener('click', () => { const currentTheme = document.body.getAttribute('data-theme') || 'dark'; const versions = configManager.config?.dbSettings?.versions || {}; window.electronAPI.openAcknowledgementsWindow(currentTheme, versions); }); document.getElementById('author-website-btn')?.addEventListener('click', () => window.electronAPI.openExternalLink('https://blog.ymhut.cn')); // 工具健康检查按钮 document.getElementById('tool-health-check-btn')?.addEventListener('click', async () => { const btn = document.getElementById('tool-health-check-btn'); if (btn.disabled) return; // 防止重复点击 const progressContainer = document.getElementById('tool-health-progress-container'); const progressStatus = document.getElementById('tool-health-progress-status'); const progressText = document.getElementById('tool-health-progress-text'); const progressBar = document.getElementById('tool-health-progress-bar'); const progressSuccess = document.getElementById('tool-health-progress-success'); const progressFailed = document.getElementById('tool-health-progress-failed'); const progressPending = document.getElementById('tool-health-progress-pending'); btn.disabled = true; const btnContent = btn.querySelector('.tool-health-btn-content'); if (btnContent) { btnContent.innerHTML = ` ${i18n.t('settings.toolHealth.checking', '检查中...')}`; } else { btn.innerHTML = ` ${i18n.t('settings.toolHealth.checking', '检查中...')}`; } // 显示进度容器 if (progressContainer) { progressContainer.style.display = 'block'; // 重置进度 if (progressText) progressText.textContent = '0 / 0'; if (progressBar) progressBar.style.width = '0%'; if (progressSuccess) progressSuccess.textContent = '0'; if (progressFailed) progressFailed.textContent = '0'; if (progressPending) progressPending.textContent = '0'; } try { // [重构] 直接使用 toolHealthChecker,不显示模态框 if (!window.toolHealthChecker) { let retries = 0; while (!window.toolHealthChecker && retries < 20) { await new Promise(resolve => setTimeout(resolve, 100)); retries++; } } if (window.toolHealthChecker) { // 设置进度更新回调 const progressCallback = (progress) => { const { checked = 0, total = 0, success = 0, failed = 0 } = progress; // 更新设置页面的进度显示 if (progressText) { progressText.textContent = `${checked} / ${total}`; } if (progressBar) { const percent = total > 0 ? (checked / total) * 100 : 0; progressBar.style.width = `${percent}%`; } if (progressSuccess) progressSuccess.textContent = String(success); if (progressFailed) progressFailed.textContent = String(failed); if (progressPending) { const pending = total - checked; progressPending.textContent = String(pending); } if (progressStatus) { if (checked < total) { progressStatus.innerHTML = `${i18n.t('settings.toolHealth.checking', '检查中...')}`; } else { progressStatus.innerHTML = `${i18n.t('toolHealthCheck.completed', '检查完成')}`; } } }; // 注册进度回调 window.toolHealthChecker.onProgress(progressCallback); // 强制检查(force = true),忽略每日检查限制 await window.toolHealthChecker.checkAllTools(true, false); // 移除进度回调 window.toolHealthChecker.offProgress(progressCallback); // 检查完成后更新UI this.updateLockedToolsList(); this.updateToolHealthStats(); } else { uiManager.showNotification('错误', '工具健康检查模块未加载', 'error'); } } catch (error) { console.error('工具健康检查失败:', error); uiManager.showNotification('检查失败', error.message || '未知错误', 'error'); if (progressStatus) { progressStatus.innerHTML = `${i18n.t('toolHealthCheck.error', '检查失败')}`; } } finally { btn.disabled = false; const btnContent = btn.querySelector('.tool-health-btn-content'); if (btnContent) { btnContent.innerHTML = ` ${i18n.t('settings.toolHealth.startCheck', '开始检查')}`; } else { btn.innerHTML = ` ${i18n.t('settings.toolHealth.startCheck', '开始检查')}`; } // 延迟隐藏进度容器,让用户看到最终结果 setTimeout(() => { if (progressContainer) { progressContainer.style.display = 'none'; } }, 3000); } }); // 更新已锁定工具列表和统计信息 this.updateLockedToolsList(); this.updateToolHealthStats(); // [新增] 绑定锁定工具卡片点击事件 const lockedCard = document.getElementById('tool-health-locked-card'); if (lockedCard) { lockedCard.addEventListener('click', () => { this.showLockedToolsModal(); }); } // [重构] 模态框事件绑定已由 ModalManager 统一处理,无需手动绑定 // 通用模态框框架会自动处理关闭按钮、背景点击和ESC键 document.getElementById('feedback-btn')?.addEventListener('click', () => window.electronAPI.openExternalLink('mailto:admin@ymhut.cn')); this.renderTrafficChart(); } /** * [重构] 更新已锁定工具列表 * 使用 toolStatusManager 统一管理锁定状态 * 支持模态框显示(使用通用模态框框架) */ updateLockedToolsList() { const lockedListEl = document.getElementById('tool-health-locked-list'); const lockedCardEl = document.getElementById('tool-health-locked-card'); // [健康检查预留位置] 使用 toolStatusManager 获取锁定工具列表 // 动态导入 toolStatusManager 以避免循环依赖 import('./toolStatusManager.js').then(module => { const toolStatusManager = module.default; const lockedTools = toolStatusManager.getLockedTools(); // 更新统计卡片显示 if (lockedListEl) { if (lockedTools.length > 0) { const count = lockedTools.length; lockedListEl.innerHTML = `${count} 个工具已锁定`; // 如果有锁定工具,卡片可点击 if (lockedCardEl) { lockedCardEl.classList.add('has-locked'); } } else { lockedListEl.innerHTML = `${i18n.t('settings.toolHealth.noLocked', '暂无锁定工具')}`; // 没有锁定工具,移除可点击状态 if (lockedCardEl) { lockedCardEl.classList.remove('has-locked'); } } } // [重构] 如果模态框已打开(使用通用模态框框架),更新其内容 import('./ui/ModalManager.js').then(({ default: modalManager }) => { if (modalManager.isOpen('tool-health-locked-modal')) { const modalContent = this._generateLockedToolsContent(lockedTools, toolStatusManager); modalManager.updateContent('tool-health-locked-modal', `
    ${modalContent}
    ` ); } }).catch(() => { // 如果 ModalManager 未加载,忽略 }); }).catch((error) => { console.error('[MainPage] 更新锁定工具列表失败:', error); // 如果导入失败,延迟重试 setTimeout(() => this.updateLockedToolsList(), 500); }); } /** * [新增] 生成锁定工具内容HTML * @private */ _generateLockedToolsContent(lockedTools, toolStatusManager) { if (lockedTools.length === 0) { return `

    暂无锁定工具

    所有工具运行正常
    `; } return lockedTools.map(toolId => { let toolName = toolId; let toolDescription = '工具已锁定'; // 尝试获取工具信息 if (window.toolRegistry && window.toolRegistry[toolId]) { const toolClass = window.toolRegistry[toolId]; toolName = toolClass.name || toolId; toolDescription = toolClass.description || '工具已锁定'; } else if (window.uiManager && window.uiManager.modularTools) { const toolMeta = window.uiManager.modularTools[toolId]; if (toolMeta) { toolName = toolMeta.name || toolId; toolDescription = toolMeta.description || '工具已锁定'; } } const status = toolStatusManager.getToolStatus(toolId); const lockReason = status?.lockReason || '健康检查失败'; const lockTime = status?.lastHealthCheck ? new Date(status.lastHealthCheck).toLocaleString('zh-CN') : '未知时间'; return `
    ${toolName}
    ${toolDescription}
    ${lockReason} ${lockTime}
    `; }).join(''); } /** * [重构] 显示锁定工具模态框(使用通用模态框框架) */ async showLockedToolsModal() { // 动态导入 ModalManager const { default: modalManager } = await import('./ui/ModalManager.js'); // 获取锁定工具列表 const lockedTools = await this._getLockedToolsList(); // 生成内容HTML let contentHtml = ''; if (lockedTools.length > 0) { contentHtml = lockedTools.map(tool => { const toolName = tool.name || tool.id; const toolDescription = tool.description || '工具已锁定'; const lockReason = tool.lockReason || '健康检查失败'; const lockTime = tool.lockTime || '未知时间'; return `
    ${toolName}
    ${toolDescription}
    ${lockReason} ${lockTime}
    `; }).join(''); } else { contentHtml = `

    暂无锁定工具

    所有工具运行正常
    `; } // 使用通用模态框框架创建模态框 modalManager.create({ id: 'tool-health-locked-modal', title: ' 已锁定工具', content: `
    ${contentHtml}
    `, size: 'medium', closable: true, closeOnBackdrop: true, className: 'tool-health-locked-modal-wrapper', onClose: () => { // 关闭回调 } }); } /** * [重构] 隐藏锁定工具模态框(使用通用模态框框架) */ async hideLockedToolsModal() { const { default: modalManager } = await import('./ui/ModalManager.js'); modalManager.close('tool-health-locked-modal'); } /** * [新增] 获取锁定工具列表 * @private */ async _getLockedToolsList() { try { const { default: toolStatusManager } = await import('./toolStatusManager.js'); const lockedTools = toolStatusManager.getLockedTools(); return lockedTools.map(toolId => { let toolName = toolId; let toolDescription = '工具已锁定'; // 尝试获取工具信息 if (window.toolRegistry && window.toolRegistry[toolId]) { const toolClass = window.toolRegistry[toolId]; toolName = toolClass.name || toolId; toolDescription = toolClass.description || '工具已锁定'; } const status = toolStatusManager.getToolStatus(toolId); return { id: toolId, name: toolName, description: toolDescription, lockReason: status?.lockReason || '健康检查失败', lockTime: status?.lastHealthCheck ? new Date(status.lastHealthCheck).toLocaleString('zh-CN') : '未知时间' }; }); } catch (error) { console.error('[MainPage] 获取锁定工具列表失败:', error); return []; } } /** * [重构] 显示检查历史记录模态框(使用通用模态框框架) */ async showToolHealthHistoryModal() { // [日志] 记录查看检查历史 if (configManager && configManager.logAction) { configManager.logAction('查看工具健康检查历史记录', 'ui'); } // 动态导入 ModalManager const { default: modalManager } = await import('./ui/ModalManager.js'); // 显示加载状态 const loadingContent = `

    加载中...

    正在获取检查历史记录
    `; // 创建模态框(先显示加载状态) const modal = modalManager.create({ id: 'tool-health-history-modal', title: ' 检查历史记录', content: `
    ${loadingContent}
    `, size: 'large', closable: true, closeOnBackdrop: true, className: 'tool-health-history-modal-wrapper' }); // 获取检查历史记录 try { if (window.electronAPI && window.electronAPI.getAllToolHealthCheckSummaries) { const result = await window.electronAPI.getAllToolHealthCheckSummaries(50); if (result && result.success && result.data && result.data.length > 0) { // 显示历史记录列表 const historyContent = result.data.map(summary => { const checkDate = new Date(summary.check_date); const formatted = checkDate.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); const successCount = summary.success_count || 0; const failedCount = summary.failed_count || 0; const totalCount = summary.total_count || 0; return `
    ${formatted}
    正常: ${successCount} 异常: ${failedCount} 总计: ${totalCount}
    `; }).join(''); // 更新模态框内容 modalManager.updateContent('tool-health-history-modal', `
    ${historyContent}
    ` ); } else { // 没有历史记录 modalManager.updateContent('tool-health-history-modal', `

    暂无检查记录

    尚未进行健康检查
    ` ); } } else { modalManager.updateContent('tool-health-history-modal', `

    无法加载历史记录

    数据库接口不可用
    ` ); } } catch (error) { console.error('[MainPage] 加载检查历史记录失败:', error); modalManager.updateContent('tool-health-history-modal', `

    加载失败

    ${error.message || '未知错误'}
    ` ); } } /** * [重构] 隐藏检查历史记录模态框(使用通用模态框框架) */ async hideToolHealthHistoryModal() { const { default: modalManager } = await import('./ui/ModalManager.js'); modalManager.close('tool-health-history-modal'); } /** * 更新工具健康检查统计信息 */ updateToolHealthStats() { // [优化] 更新上次检查时间(从数据库获取最新记录) const lastCheckTimeEl = document.getElementById('tool-health-last-check-time'); const lastCheckCard = document.getElementById('tool-health-last-check-card'); if (lastCheckTimeEl) { // 从数据库获取最新的检查时间 if (window.electronAPI && window.electronAPI.getLastHealthCheckDate) { window.electronAPI.getLastHealthCheckDate().then(result => { if (result && result.success && result.data) { const lastCheckDate = result.data; if (lastCheckDate) { const date = new Date(lastCheckDate); const formatted = date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); lastCheckTimeEl.textContent = formatted; lastCheckTimeEl.style.color = 'var(--text-primary)'; // [优化] 如果有检查记录,使卡片可点击 if (lastCheckCard) { lastCheckCard.classList.add('has-history'); } } else { lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查'); lastCheckTimeEl.style.color = 'var(--text-secondary)'; } } else { lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查'); lastCheckTimeEl.style.color = 'var(--text-secondary)'; } }).catch(() => { lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查'); lastCheckTimeEl.style.color = 'var(--text-secondary)'; }); } else { // 降级方案:使用 toolHealthChecker 的 lastCheckDate if (window.toolHealthChecker) { const lastCheckDate = window.toolHealthChecker.lastCheckDate; if (lastCheckDate) { const date = new Date(lastCheckDate); const formatted = date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); lastCheckTimeEl.textContent = formatted; lastCheckTimeEl.style.color = 'var(--text-primary)'; if (lastCheckCard) { lastCheckCard.classList.add('has-history'); } } else { lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查'); lastCheckTimeEl.style.color = 'var(--text-secondary)'; } } else { lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查'); lastCheckTimeEl.style.color = 'var(--text-secondary)'; } } } // [新增] 绑定检查历史记录模态框事件 if (lastCheckCard) { // 移除旧的事件监听器(如果存在) const newLastCheckCard = lastCheckCard.cloneNode(true); lastCheckCard.parentNode?.replaceChild(newLastCheckCard, lastCheckCard); const updatedLastCheckCard = document.getElementById('tool-health-last-check-card'); if (updatedLastCheckCard) { updatedLastCheckCard.addEventListener('click', () => { this.showToolHealthHistoryModal(); }); } } // [优化] 更新网络工具总数(使用动态导入避免依赖问题) const totalCountEl = document.getElementById('tool-health-total-count'); if (totalCountEl) { // 使用动态导入获取 toolRegistry import('./tool-registry.js').then(({ toolRegistry }) => { if (window.toolHealthChecker && toolRegistry) { const allTools = Object.keys(toolRegistry); const networkTools = allTools.filter(toolId => window.toolHealthChecker.isNetworkTool(toolId) ); totalCountEl.textContent = `${networkTools.length} ${i18n.t('settings.toolHealth.tools', '个工具')}`; totalCountEl.style.color = 'var(--text-primary)'; } else if (window.toolHealthChecker) { // 如果 toolHealthChecker 可用但 toolRegistry 未加载,延迟重试 setTimeout(() => this.updateToolHealthStats(), 500); } else { totalCountEl.textContent = i18n.t('settings.toolHealth.calculating', '计算中...'); totalCountEl.style.color = 'var(--text-secondary)'; // 延迟重试 setTimeout(() => this.updateToolHealthStats(), 1000); } }).catch(() => { // 如果导入失败,延迟重试 totalCountEl.textContent = i18n.t('settings.toolHealth.calculating', '计算中...'); totalCountEl.style.color = 'var(--text-secondary)'; setTimeout(() => this.updateToolHealthStats(), 1000); }); } } /** * 处理语言切换 * @param {string} targetLang - 目标语言代码 ('auto', 'zh-CN', 'en-US') */ async handleLanguageChange(targetLang) { // 如果是auto,直接保存并重启 if (targetLang === 'auto') { uiManager.showNotification(i18n.t('common.notification.title.info'), '正在保存语言设置并重启...', 'info'); try { await window.electronAPI.saveLanguageConfig(targetLang); } catch (err) { uiManager.showNotification(i18n.t('common.notification.title.error'), err.message, 'error'); } return; } // 获取当前语言配置 const langConfig = await window.electronAPI.getLanguageConfig(); const currentLang = langConfig.actual || 'zh-CN'; // 如果目标语言和当前语言相同,直接返回 if (targetLang === currentLang) { uiManager.showNotification(i18n.t('common.notification.title.info'), '语言设置已保存', 'info'); await window.electronAPI.saveLanguageConfig(targetLang); return; } // 检查目标语言包是否存在 const hasLangPack = langConfig.pack && Object.keys(langConfig.pack).length > 0 && targetLang !== 'zh-CN'; // 如果语言包不存在或需要翻译,显示翻译遮罩层 if (!hasLangPack && targetLang !== 'zh-CN') { // 动态导入翻译服务 const { default: translationService } = await import('./translationService.js'); const { default: translationOverlay } = await import('./translationOverlay.js'); // 显示翻译遮罩层 translationOverlay.show(i18n.t('translation.progress') || '正在准备翻译...', 0); try { // 获取源语言包(中文) const sourcePack = langConfig.fallback || {}; // 确定目标语言代码 const targetLangCode = targetLang === 'en-US' ? 'en' : targetLang.split('-')[0]; const sourceLangCode = 'zh'; // 翻译语言包 let translatedCount = 0; const totalKeys = Object.keys(sourcePack).length; const translatedPack = await translationService.translateObject( sourcePack, targetLangCode, sourceLangCode, (current, total, key) => { translatedCount = current; const progress = (current / total) * 100; translationOverlay.updateProgress( i18n.t('translation.progress') || '正在翻译语言包...', progress, key ); } ); // 保存翻译后的语言包 translationOverlay.updateProgress(i18n.t('translation.saving') || '正在保存翻译结果...', 95); await window.electronAPI.saveTranslatedLanguagePack(targetLang, translatedPack); // 保存语言配置 translationOverlay.updateProgress('正在保存语言设置...', 98); await window.electronAPI.saveLanguageConfig(targetLang); // 隐藏遮罩层 translationOverlay.hide(); // 显示成功消息并重启 uiManager.showNotification( i18n.t('common.notification.title.success'), i18n.t('translation.complete') || '语言包翻译完成,正在重启应用...', 'success' ); // 延迟重启,让用户看到消息 setTimeout(() => { window.location.reload(); }, 1000); } catch (error) { translationOverlay.hide(); console.error('[Language] Translation failed:', error); uiManager.showNotification( i18n.t('common.notification.title.error'), `${i18n.t('translation.failed') || '翻译失败'}: ${error.message}。将使用默认语言包。`, 'error' ); // 即使翻译失败,也保存语言设置(使用已有的语言包) try { await window.electronAPI.saveLanguageConfig(targetLang); } catch (err) { console.error('[Language] Failed to save language config:', err); } } } else { // 语言包已存在,直接保存并重启 uiManager.showNotification(i18n.t('common.notification.title.info'), '正在保存语言设置并重启...', 'info'); try { await window.electronAPI.saveLanguageConfig(targetLang); } catch (err) { uiManager.showNotification(i18n.t('common.notification.title.error'), err.message, 'error'); } } } } const mainPageInstance = new MainPage(); window.mainPage = mainPageInstance; // 暴露工具健康检查模块到全局(延迟加载) setTimeout(async () => { try { const toolHealthCheckerModule = await import('./toolHealthChecker.js'); window.toolHealthChecker = toolHealthCheckerModule.default; // [重构] 不再暴露模态框版本的工具健康检查到全局 // const toolHealthCheckModalModule = await import('./toolHealthCheckModal.js'); // window.toolHealthCheckModal = toolHealthCheckModalModule.default; // [重构] 暴露工具箱验证页面 const toolboxVerificationPageModule = await import('./toolboxVerificationPage.js'); window.toolboxVerificationPage = toolboxVerificationPageModule.default; } catch (error) { console.error('Failed to load tool health check modules:', error); } }, 100); // [修复] 确保 DOM 加载后再启动初始化,防止白屏 document.addEventListener('DOMContentLoaded', () => { mainPageInstance.init(); });